webpack搭建项目流程(纯干货)

2021-01-03 11:28

阅读:696

css-loader是为了处理import require @import url 这样的引入

style-loader 是动态创建一个style 标签,然后把样式塞进去 添加到页面中的

less-loader 是编译less语法

执行顺序:如果loader选项是一个数组 那执行顺序是从右向左的 loader:[‘style-loader‘,‘css-loader‘] 配置多个loader的话 是从下到上的执行顺序的

因此我们需要给我们的less文件同样配置loader


  // 同样的需要先安装

  npm install style-loader css-loader less-loader --save-dev

  // build/webpack.config.js
  module:{
        rules:[
            {
                test:/\.jsx?$/,
                exclude:/node_modules/,
                loader:‘babel-loader‘
            },
            {
                test:/\.css$/,
                use:[
                    {
                        loader:"style-loader"
                    },
                    {
                        loader:"css-loader"
                    }
                ]
            },
            {
                test:/\.less$/,
                use:[
                    {
                        loader:"style-loader"
                    },
                    {
                        loader:"css-loader"
                    },
                    {
                        loader:"less-loader"
                    }
                ]
            }
        ]
    }

正常情况下执行npm run dev 项目就会跑起来,

如果出现以下报错:

技术图片

 

 

 这个报错说我们缺少less模块, less-loader我们安装的版本是7.0.0的版本,也就是说这个版本里面不在包含less包需要我们单独安装less包了

//执行以下命令安装以下就好了
npm install less --save-dev 

到此我们的项目就已经成功跑起来了 并且没有报错了

技术图片

 

 

 

配置入口html -- html-webpack-plugin插件

当我们打开 http://localhost:8080 的时候竟然是这样的?

技术图片

 

 

 是不是一脸蒙蔽?别怕,是因为我们尽管已经把工程跑起来了,但是我们并没有给他指定一个入口html,他这里是webpack-dev-server中默认的页面,所以我们就需要使用html-webpack-plugin来配置我们的入口html

// html-webpack-plugin 插件用法参考官网 
// 我们需要在webpack.config.js中增加plugins选项 来配置我们的入口html

// 同样首先需要安装一下html-webpack-plugin插件
npm install html-webpack-plugin --save-dev

// build/webpack.config.js 增加plugins选项
// 安装html-webpack-plugin
const HtmlWebpackPlugin = require("html-webpack-plugin")

plugins:[
    new HtmlWebpackPlugin({
        filename:path.resolve(__dirname,"../dist/index.html"),
        template:path.resolve(__dirname,"../public/index.html"),
        inject:true,// 注入选项 有四个值 true,body(script标签位于body底部),head,false(不插入js文件)
        hash:true,//回给script标签中的js文件增加一个随机数 防止缓存 bundle.js?22b9692e22e7be37b57e
    })
]

重新执行npm run dev 打开http://localhost:8080 就会看到我们写的Hello world!

技术图片

 

 到现在为止我们已经完成了工程的基础架构,如果是一些简单的页面上面的流程已经完全够用了,但是对于一些大中型的项目仍然需要做一些优化,下面会讲一下如何优化我们的工程

 

项目优化

这一部分我们会使用一些常用的插件来优化我们的构建流程,提升我们的构建时间和减少我们构建包体积大小

区分不同环境

我们日常工作中都是存在好几套环境的,测试环境、预生产环境、生产环境等等。因此在不同环境使用不同的配置项是有必要的。比如我们在测试环境没有必要压缩我们的代码,这样调试起来比较方便等等。下面我们来看一下区分不同环境需要怎么搞?


第一种方法:使用cross-env设置不同的环境变量,然后判断环境变量来使用不同的配置

cross-env的作用就不在多说了,简单总结就是他可以跨不同环境设置变量的值

 

   // 安装 cross-env
   npm install cross-env --save-dev 

   // package.json 增加测试命令 
   "build:test": "cross-env APP_ENV=test  webpack --config build/webpack.config.js",

   // webpack.config.js 增加测试代码
   // 我们根据APP_ENV不同的值来把构建的js文件放到不同的文件夹下面
   output:{
       path:process.env.APP_ENV === ‘test‘ ? path.resolve(__dirname,"../test"): path.resolve(__dirname,"../dist"),
       filename:‘js/[name].js‘,
       publicPath:"/" //打包后的资源的访问路径前缀
   },

 

修改完之后,执行 npm run build:test 命令,会打包生成一个test文件夹,如图所示:

技术图片

 

 

 除了可以配置不同的输出目录之前也可以根据环境变量的值判断是否需要一些其他的插件等等从而实现不同环境,不同配置的目标

 

第二种方法:生成不同的配置文件,执行相应的命令来执行不同的配置文件

首先我们复制一份webpack.config.js,然后分别命名为webpack.dev.config.js和webpack.prod.config.js 分别对应我们在development和production环境的配置,但是我们发现他们之前有很多的共同配置,因此我们还有把共同的配置抽离出来webpack.base.config.js文件。然后通过webpack-merge插件把他们合并起来。 具体实现如下:


 
   // 安装依赖 webpack-merge 
   npm install webpack-merge --save-dev 

   // webpack.base.config.js
   
   const path = require("path")

   module.exports = {
       //入口
       entry:{
           app:‘./src/index‘
       },
       output:{
           path:path.resolve(__dirname,"../dist"),
           filename:‘js/[name].js‘,
           publicPath:"/" //打包后的资源的访问路径前缀
       },
       module:{
           rules:[
               {
                   test:/\.jsx?$/,
                   exclude:/node_modules/, // 这个node_modules文件夹里面的js/jsx文件不需要使用babel-loader
                   loader:‘babel-loader‘
                   // babel-loader的参数配置也可以这样写,我们这里是新建一个.babelrc文件的方式来配置
                   // use: {  
                   //     loader: ‘babel-loader‘,
                   //     options: {
                   //     presets: [‘@babel/preset-env‘]
                   //     }
                   // }
               },
               {
                   test:/\.css$/,
                   use:[
                       {
                           loader:"style-loader"
                       },
                       {
                           loader:"css-loader"
                       }
                   ]
               },
               {
                   test:/\.less$/,
                   use:[
                       {
                           loader:"style-loader"
                       },
                       {
                           loader:"css-loader"
                       },
                       {
                           loader:"less-loader"
                       }
                   ]
               }
           ]
       },
   }

   // webpack.dev.config.js
       
   const path = require("path")
   // 安装html-webpack-plugin
   const HtmlWebpackPlugin = require("html-webpack-plugin")
   const webpackBaseConfig = require("./webpack.base.config.js")
   // 根据不同规则合并两个配置项,
   const { merge } = require("webpack-merge")

   module.exports = merge(webpackBaseConfig,{
       // 指定构建环境
       mode: "development",
       plugins:[
           new HtmlWebpackPlugin({
               filename:path.resolve(__dirname,"../dist/index.html"),
               template:path.resolve(__dirname,"../public/index.html"),
               inject:true,// 注入选项 有四个值 true,body(script标签位于body底部),head,false(不插入js文件)
           })
       ]
   })

   // webpack.prod.config.js
   
   const path = require("path")
   // 安装html-webpack-plugin
   const HtmlWebpackPlugin = require("html-webpack-plugin")

   const webpackBaseConfig = require("./webpack.base.config.js")
   const { merge } = require("webpack-merge")


   module.exports = merge(webpackBaseConfig,{
       // 指定构建环境
       mode: "production",
       plugins:[
           new HtmlWebpackPlugin({
               filename:path.resolve(__dirname,"../dist/index.html"),
               template:path.resolve(__dirname,"../public/index.html"),
               inject:true,// 注入选项 有四个值 true,body(script标签位于body底部),head,false(不插入js文件)
               hash:true,//回给script标签中的js文件增加一个随机数 防止缓存 bundle.js?22b9692e22e7be37b57e
               //压缩选项 会有很多选项 可以去npm官网上查看对应的配置
               minify:{
                   removeComments:true,//去注释
                   collapseWhitespace:true,//去空格
                   removeAttributeQuotes:true, // 去属性的引号
               }
           })
       ]
   })
   

   // 最后修改package.json中的打包命令配置文件
   "scripts": {
       "test": "echo \"Error: no test specified\" && exit 1",
       "dev": "webpack-dev-server --config build/webpack.dev.config.js",
       "build": "webpack --config build/webpack.prod.config.js"
   },

到这里我们就把不同环境的配置文件分离开来,这样后面再增加配置的时候就会更加清晰。

压缩js

原来我们构建的时候压缩js使用的是uglifyjs-webpack-plugin,但是webpack4以后官网推荐使用terser-webpack-plugin terser-webpack-plugin 也是webpack 4的内置插件(也就是说我们不需要自己添加这个插件就可以压缩js代码,但是如果我们不想使用默认的配置的话 需要重新定义) 使用方法如下:


   // 安装插件terser-webpack-plugin
   npm install terser-webpack-plugin --save-dev
   // 因为这个插件只会在生产环境生效也就是说这个配置我们要写在webpack.prod.config.js中
   // webpack.prod.config.js  增加optimization选项 (和plugins/mode平级的)
   // Optimization 这个选项是可以覆盖webpack内置的一些配置 比如 压缩、分包机制等等
   optimization:{
       minimizer:[
           new TerserWebpackPlugin({
               parallel:true,
               sourceMap:false,
               exclude:/\/node_modules/,
               extractComments: true, // 这个选项如果为true 会生成一个app.js.LICENSE.txt文件 存储特定格式的注释
               terserOptions:{
                   warnings:false,
                   compress:{
                       unused:true,
                       drop_debugger:true,
                       drop_console:true
                   },
               }
           })
       ]
   }

 

这个时候执行npm run build 打包的话,打包之后的提交不会有太大的改变,因为webpack4已经内置了这个插件,对我们的代码已经进行了压缩处理,但是我们还是需要了解一下这个插件的一些配置项的

提取css文件/压缩css

提取css文件

我们可以看一下我们打包生成的文件,是没有css文件的,因为webpack默认会把css当做一个模块打包到相应的js文件中去, 但是这样的话也导致我们的bundle文件比较大,所以把css单独拆分出来是有必要的。
在这里我们提取单独的css文件使用的插件是:mini-css-extract-plugin(该插件同样只在生产环境下生效)

具体的配置如下:


    // 安装插件
    npm install mini-css-extract-plugin --save-dev

    // 修改webpack.base.config.js中 增加插件mini-css-extract-plugin 
    plugins:[
        new MiniCssExtractPlugin({
            filename:‘css/[name].css‘,
            chunkFilename:‘css/[id].css‘
        })
    ],
    // 同时要修改css/less的loader  
    // 此处是截取的module --> rules里面的需要修改的代码 
    // 把style-loader注释掉是因为两个会冲突,导致报错
    {
                test:/\.css$/,
                use:[
                    {
                        loader:MiniCssExtractPlugin.loader,
                        options:{
                            hmr:true,
                            reloadAll:true
                        }
                    },
                    // {
                    //     loader:"style-loader"
                    // },
                    {
                        loader:"css-loader"
                    }
                ]
            },
            {
                test:/\.less$/,
                use:[
                    {
                        loader:MiniCssExtractPlugin.loader,
                        options:{
                            hmr:true,
                            reloadAll:true
                        }
                    },
                    // {
                    //     loader:"style-loader"
                    // },
                    {
                        loader:"css-loader"
                    },
                    {
                        loader:"less-loader"
                    }
                ]
            }

 

我们再次打包之后就可以看见已经生成了一个app.css文件

 

技术图片

 

 

压缩css

我们打开刚打包的app.css文件可以看到并没有对内容进行压缩,

技术图片

 

 

 下面我们来实现把css压缩的功能,这里我们使用的插件是:optimize-css-assets-webpack-plugin 具体实现如下:

 

// 安装插件 optimize-css-assets-webpack-plugin
npm install optimize-css-assets-webpack-plugin --save-dev 

//同样修改optimization选项 来重写默认配置
optimization:{
    minimizer:[
        new TerserWebpackPlugin({
            parallel:true,
            sourceMap:false,
            exclude:/\/node_modules/,
            extractComments: true, // 这个选项如果为true 会生成一个app.js.LICENSE.txt文件 存储特定格式的注释
            terserOptions:{
                warnings:false,
                compress:{
                    unused:true,
                    drop_debugger:true,
                    drop_console:true
                },
            }
        }),
        // 压缩css
        new OptimizeCssAssetsPlugin({
            cssProcessorOptions:{safe:true,discardComments:{removeAll:true}}
        })
    ]
}

 

再次打包之后,打开app.css里面的代码已经被压缩过了

技术图片

 

 

 

清空构建目录/构建包分析

清空构建目录

现在我们打包的话生成的app.js文件都没有带随机数来防止缓存,我们现在加上这个功能: 就是在我们的output里面加一个配置

技术图片

 

 

然后我们就可以看到我们的dist文件夹里面会出现这样的情况:

 

技术图片

 

 

也就是说我们以前构建的文件都没有被删除
webpack提供了一个插件来帮助我们删除原来的dist目录:clean-webpack-plugin 不需要什么特殊的配置,直接添加到plugins选项里就好了

技术图片

 

 

这样我们在重新构建之前就会清空dist目录了

构建包分析

在构建之后我们想要查看我们的构建包里面都包含什么东西,方便我们后期进行优化,这个时候就需要用到包分析插件了,
我们使用webpack-bundle-analyzer来实现这个功能。
同样的添加到plugins选项中就好了:

技术图片

 

 

打包结束的时候会自动打开浏览器,展示对应的包大小和包里面包含的内容:

技术图片

 

 

 

构建包拆分

webpack3中拆包使用 common-chunk-plugin拆包 具体用法参考百度 webpack4中通过设置splitChunks参数 splitChunks默认参数:(是在optimization中的一个property)

 splitChunks: {
   // async表示只从异步加载得模块(动态加载import())里面进行拆分(会拆分出通过懒加载等方式异步加载的模块)
   // initial表示只从入口模块进行拆分(入口文件会包含node_modules中的react-dom等包,但是在blog.js中异步加载的marterial等插件就没有拆分出来 和业务代码打包成了一个包)
   // all表示以上两者都包括
   chunks: "async",
   minSize: 30000,   // 大于30k会被webpack进行拆包
   minChunks: 1,     // 被引用次数大于等于这个次数进行拆分
   // import()文件本身算一个
   // 只计算js,不算css
   // 如果同时有两个模块满足cacheGroup的规则要进行拆分,但是maxInitialRequests的值只能允许再拆分一个模块,那尺寸更大的模块会被拆分出来
   maxAsyncRequests: 5,  // 最大的按需加载(异步)请求次数
   // 最大的初始化加载请求次数,为了对请求数做限制,不至于拆分出来过多模块
   // 入口文件算一个
   // 如果这个模块有异步加载的不算
   // 只算js,不算css
   // 通过runtimeChunk拆分出来的runtime不算在内
   // 如果同时又两个模块满足cacheGroup的规则要进行拆分,但是maxInitialRequests的值只能允许再拆分一个模块,那尺寸更大的模块会被拆分出来
   maxInitialRequests: 3,
   automaticNameDelimiter: ‘~‘, // 打包分隔符
   name:true,
   cacheGroups: {
       // 默认的配置
       vendors: {
           test: /[\\/]node_modules[\\/]/,
           priority: -10
       },
       // 默认的配置,vendors规则不命中的话,就会命中这里
       default: {
           minChunks: 2, // 引用超过两次的模块 -> default
           priority: -20,
           reuseExistingChunk: true
       },
   },
 }

我们这里使用 chunks:"all" 方式拆分的结果如下:

技术图片

 

 

 技术图片

 

 

静态资源打包

一般我们的项目中的静态资源是不需要webpack处理的只需要把他们复制到对应的构建目录中就可以了, 我们使用的插件是:copy-webpack-plugin

技术图片

 

 

多页应用打包配置

目前我们的这个应用是单页应用,如果要是多页应用的话需要我们增加多个入口和多个出口、以及增加对应的html模板文件 也就是说我们直接改entry,以及增加html模板就好了

  // 首先增加我们的多页应用 目录结构如下
  // 我们把不同的页面的文件拆分开 这样后期处理起来比较方便,结构也比较清晰
  -src
   - detail
    - index.js
   - index
    - css
     - index.css
    - index.js
  //修改entry 增加入口
    entry:{
        app:‘./src/index/index‘,
        detail:"./src/detail/index"
    },
  // 增加html模板 
    plugins:[
        new HtmlWebpackPlugin({
            filename:path.resolve(__dirname,"../dist/index.html"),
            template:path.resolve(__dirname,"../public/index.html"),
            inject:true,
            chunks:[‘app‘] // 这个和entry中的key对应 代表这个入口html需要引入哪个依赖的文件
        }),
        new HtmlWebpackPlugin({
            filename:path.resolve(__dirname,"../dist/detail.html"),
            template:path.resolve(__dirname,"../public/index.html"),
            inject:true,
            chunks:[‘detail‘]
        })
    ]
  

这样写的话 当页面比较少的话可以一个一个加 但是当页面比较多的时候不够智能,这个时候我们可以使用glob插件 来直接拿到我们src文件夹下面的page文件夹下面的文件名,然后编写函数来实现动态的设置入口和html模板

// 安装glob插件
npm install glob --save-dev 

// build文件夹下面增加util.js 文件
const path = require("path")
const glob = require("glob")

let chunksList = []
let entry = {}

function  getModulesList() {
    //[ ‘../src/pages/detail‘, ‘../src/pages/index‘ ]
    let modulesList = glob.sync(path.resolve(__dirname,‘../src/pages/*‘))
    for(let i = 0,len = modulesList.length; i){
        let moduleName = modulesList[i].split(‘/‘).slice(-1).join()
        chunksList.push(moduleName)
        entry[moduleName] = path.resolve(__dirname,"../src/pages/"+moduleName+‘/index.js‘)
    }
}

getModulesList()


module.exports = {
    resolve:function (dir) {
        return path.resolve(__dirname,dir)
    },
    entry:entry,
    chunks:chunksList
}

通过上面的函数能够拿到entry,然后同样也可以得到对应的html模板

其他优化

打包提示信息优化

我们在打包的时候会有build信息打印出来,但是我们并不关注这些东西,所以我们可以把这些信息直接去掉。 具体实现如下:.

技术图片

 

 

总结

到这里的话我们就已经实现了一个相对完整的webpack构建的项目代码。其中除了说明怎么配置之外还加入了很多为什么的思考,希望大家看完之后不仅仅知道怎么样去搭建一个项目,而是知道为什么

思考

  1. 为什么loader的执行顺序是从右向左,从下向上(提示:compose与pipe)

  2. terser-webpack-plugin插件和uglifyjs-webpack-plugin插件的区别

 


评论


亲,登录后才可以留言!