美文网首页
Webpack 4.x 小白成神之路

Webpack 4.x 小白成神之路

作者: js_冠荣 | 来源:发表于2018-05-09 19:30 被阅读0次

    前言

    趁工作之余玩了下webpack,现在都出到4.8版本了,不得不感叹前端技术版本更新真的很快!如果你还不会webpack,那你绝不是一个合格的前端开发者。

    what is webpack?

    webpack可以看做一个模块化打包机,分析项目结构,处理模块化依赖,转换成为浏览器可运行的代码。

    初始化项目

    // 这里不做过多描述
    npm init
    

    新建src文件夹,并新建index.js,随便写几行js。

    安装webpack

    webpack4.0以上抽离出了webpack-cli,所以我们需要下载2个依赖。

    npm install webpack webpack-cli -D   
    
    • npm i -D 是 npm install --save-dev 的简写,是指安装模块并保存到 package.json 的 devDependencies中,主要在开发环境中的依赖包。

    webpack4可以支持0配置打包,不过在实际开发中,0配置是根本无法满足需求的。不过我们看看到底0配置了什么?

    // node v8.2版本以上会有npx
    
    npx webpack     // 不设置mode的情况下 打包出来的文件自动压缩
    
    npx webpack --mode development  // 设置mode为开发模式,打包后的文件不被压缩
    

    当执行npx webpack命令时,webpack会自动寻找项目中src目录下的index.js文件,然后进行打包,生成一个dist目录并生成一个打包好的main.js文件

    • 启动devServer需要安装一下webpack-dev-server
    npm i webpack-dev-server -D
    

    定制化配置webpack

    webpack需要在项目根目录下创建一个webpack.config.js来导出webpack的配置,配置多样化,可以自行定制。

    // webpack.config.js
    
    const path = require('path');
    
    module.exports = {
        entry: './src/index.js',    // 入口文件
        output: {
            filename: 'bundle.js',      // 打包后的文件名称
            path: path.resolve('dist')  // 打包后的目录,必须是绝对路径
        }
    }
    
    • entry代表入口
    • output输出文件配置
    • path输出的文件地址
    • filename输出文件的名字
      接下来我们执行命令webpack,此时我们可以看到目录下多出了一个dist文件,并生成了bundle.js。
      实际开发中我们打包编译的时候一般都通过npm执行的命令,所以我们找到package.json里的执行脚本去配置一下命令,这里如下图所示:
      image.png

    npm run build 执行打包命令
    rimraf dist 每次打包前 删除dist文件
    set NODE_ENV=buildset NODE_ENV=dev 设置环境变量来区分开发环境和生成环境
    --open 自动打开默认浏览器
    npm run dev 开发环境下打包的文件,但是devServer帮我们把文件放到内存中,所以并不会输出打包后的dist文件夹

    多文件入口

    • 实际开发中基本都是spa单页面,但是我们这里还是简单说一下多入口配置
    const path = require('path');
    
    module.exports = {
        // 1.写成数组的方式就可以打出多入口文件,但是打包后会合成一个
        // entry: ['./src/index.js', './src/index2.js'],
        entry: {
            index: './src/index.js',
            index2: './src/index2.js'
        },
        output: {
            // 1. filename: 'bundle.js',
            // 2. [name]就可以将出口文件名和入口文件名一一对应
            filename: '[name].[hash:6].js',      // 打包后会生成index.js和index2.js文件
            path: path.resolve('dist')
        }
    }
    

    hash 打包文件后自动加上hash值,避免缓存,取前6位。
    执行npm run build后,dist目录下生成打包好的两个js文件:index.js 和 index2.js

    压缩js

    实际开发中,我们的js一般都很大,所以需要压缩;需要用到uglifyjs-webpack-plugin插件

    npm i uglifyjs-webpack-plugin -D
    

    接下来配置下:

    const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
    module.exports = {
        plugins: [
            new UglifyJsPlugin()
        ]
    }
    

    这样就实现了js的压缩

    HTML文件的发布

    js文件都打包好了,但是我们不能每次都手动创建一个index.html去加载你的js,这样肯定是不切实际的。
    这时候需要一个html-webpack-plugin的插件来帮助我们

      npm i html-webpack-plugin -D
    

    webpack配置插件都需要在plugins中完成,ok 我们配置下:

    const path = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin'); //打包html
    
    module.exports = {
        // 1.写成数组的方式就可以打出多入口文件,但是打包后会合成一个
        // entry: ['./src/index.js', './src/index2.js'],
        entry: {
            index: './src/index.js',
            index2: './src/index2.js'
        },
        output: {
            // 1. filename: 'bundle.js',
            // 2. [name]就可以将出口文件名和入口文件名一一对应
            filename: '[name].[hash:6].js',      // 打包后会生成index.js和index2.js文件
            path: path.resolve('dist')
        },
        plugins: [
            // 通过new一下这个类来使用插件
             new HtmlWebpackPlugin({
                template: './src/index.html',
                hash: true, // 会在打包好的bundle.js后面加上hash串
                minify: {
                    minifyCSS: true, // 压缩 HTML 中出现的 CSS 代码
                    minifyJS: true, // 压缩 HTML 中出现的 JS 代码
                    removeAttributeQuotes: true
                },
            }),
        ]
    }
    
    • template 在src目录下创建一个index.html页面当做模板来用
    • hash 会在打包好的bundle.js后面加上hash串
    • minify 是对html文件进行压缩
    • removeAttributeQuotes 去掉属性的双引号
    • minifyCSS 压缩 HTML 中出现的 CSS 代码
    • minifyCSS 压缩 HTML 中出现的 JS代码
      ok 执行npm run build,此时dist目录下自动生成index.html,并自动引入了js文件

    CSS文件发布

    在webpack中打包css、less需要单独的loader去解析

    npm i style-loader css-loader less less-loader -D
    

    接下来我们配置下:

    const path = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin'); //打包html
    
    module.exports = {
        // 1.写成数组的方式就可以打出多入口文件,但是打包后会合成一个
        // entry: ['./src/index.js', './src/index2.js'],
        entry: {
            index: './src/index.js',
            index2: './src/index2.js'
        },
        output: {
            // 1. filename: 'bundle.js',
            // 2. [name]就可以将出口文件名和入口文件名一一对应
            filename: '[name].[hash:6].js',      // 打包后会生成index.js和index2.js文件
            path: path.resolve('dist')
        },
        module: {
            rules: [
                {
                    test: /\.css/,
                    exclude: /node_modules/, // 取消匹配node_modules里面的文件
                    use: ['style-loader', 'css-loader']
                },
                {
                    test: /\.less$/,
                    exclude: /node_modules/, // 取消匹配node_modules里面的文件
                    use: ['style-loader', 'css-loader', 'less-loader']
                },
            ]
        },   
        plugins: [
            // 通过new一下这个类来使用插件
             new HtmlWebpackPlugin({
                template: './src/index.html',
                hash: true, // 会在打包好的bundle.js后面加上hash串
                minify: {
                    minifyCSS: true, // 压缩 HTML 中出现的 CSS 代码
                    minifyJS: true, // 压缩 HTML 中出现的 JS 代码
                    removeAttributeQuotes: true
                },
            }),
        ]
    }
    
    • test 正则表达式,匹配文件名
    • use 数组,里面放需要执行的loader,倒序执行,从右至左。(小坑:style-loader最后执行)
    • exclude 取消匹配node_modules里面的文件
      ok 执行npm run build,此时我们发现并没有看到css文件生成,这是为什么呢?因为webpack默认把我们的css合并到bundle.js中了,如下图:
      image.png

    实际开发中,我们一般都吧css 和 js分离。

    拆分CSS

    在webpack3.x版本中可以用extract-text-webpack-plugin插件进行拆分,但这里不做过多描述,如果感兴趣的同学可以去官网查看文档。因为webpack4.x新出了mini-css-extract-plugin插件, 我们就用最新的。让我们安装一下

    npm i mini-css-extract-plugin -D
    

    接下来我们加入配置:(为了方便观看,只显示当前配置项)

    const path = require('path');
    const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 提取出来css
    
    module.exports = {
        module: {
            rules: [
                {
                    test: /\.css/,
                    exclude: /node_modules/, // 取消匹配node_modules里面的文件
                    use: ['style-loader', MiniCssExtractPlugin.loader, 'css-loader']
                },
                {
                    test: /\.less$/,
                    exclude: /node_modules/, // 取消匹配node_modules里面的文件
                    use: ['style-loader', MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
                },
            ]
        },   
        plugins: [
            // 把css从bundle.js中分离出来
            new MiniCssExtractPlugin({
                filename: "static/css/[name].[chunkhash:8].css",
            }),
        ]
    }
    

    ok 执行npm run build,我们可以看到dist目录下生成了static/css/xxx.css文件,bundle.js文件里已没有css,这样我们就做到了css的拆分!

    图片发布(css中的img)

    同样处理图片也需要相应的loader。

      npm i file-loader url-loader -D
    

    接下来我们加入配置:(为了方便观看,只显示当前配置项)

    const path = require('path');
    
    // 解决css 分离后图片引入路径不正确问题
    if (process.env.type == 'build') { // 判断package.json里面是build还是dev命令
        // 开发
        var website ={
            publicPath:"/"
        }
    } else {
        // 生产
        var website ={
            publicPath:"/"
        }
    }
    
    module.exports = {
        output: {
            filename: 'bundle.[chunkhash:6].js',
            path: path.resolve('dist'),
            publicPath: website.publicPath // 解决css 分离后图片引入路径不正确问题
        },    
        module: {
            rules: [
                {
                    test: /\.(png|jpe?g|gif|svg)$/,
                    use: [
                        {
                            loader: 'url-loader',
                            options: {
                                limit: 10000, // limit:是把小于xxx(也就是xxxk)的文件打成Base64的格式,写入JS。否则就写入路径
                                name: '[name]_[hash:7].[ext]', // 打包图片的名字
                                outputPath:'static/images/' // 打包后放到images路径下
                            }
                        }
                    ]
                },
            ]
        },   
    }
    

    需要注意如果是在css文件里引入的如背景图之类的图片,就需要指定一下相对路径!

    • limit 阈值 单位byte 大于设置值不进行base64压缩,否则下雨此值压缩成base64
    • name 打包图片的名字
    • outputPath 打包图片路径
      ok 执行npm run build,我们可以看到dist目录下生成了static/images/xxx.png, 或者直接在css已base64码引入

    图片发布(页面中的img)

    在实际开发中,我们经常用的<img src="xxxx.png"/>的方式引入图片,同样也有相应的loader去处理。

    npm i html-withimg-loader -D
    

    接下来我们加入配置:(为了方便观看,只显示当前配置项)

    module.exports = {
        module: {
            rules: [
                {
                    test: /\.(htm|html)$/,
                    use: 'html-withimg-loader'
                }
            ]
        }
    }
    

    ok,这样我们html和css中的img都可以打包成功了!

    自动处理CSS3属性前缀

    为了浏览器的兼容性,有时候我们必须加入-webkit,-ms,-o,-moz这些前缀。目的就是让我们写的页面在每个浏览器中都可以顺利运行

    PostCSS是一个CSS的处理平台,它可以帮助你的CSS实现更多的功能。

    npm i postcss-loader autoprefixer -D
    

    安装后,在项目根目录下创建一个postcss.config.js文件,配置如下:

    module.exports = {
      plugins: {
        // 处理 @import
        'postcss-import': {},
        // 处理 css 中 url
        'postcss-url': {},
        // 自动前缀
        'autoprefixer': {
          "browsers": [
            "> 1%",
            "last 2 versions"
          ]
        }
      }
    }
    

    这样我们就实现了css前缀自动补齐了。

    babel-loader(解析ES6、ES7)

    在实际开发中基本都开始使用ES6的语法了,可以提高前端开发速度,但是有一些低版本的浏览器不支持语法,所以我们需要转成es5。于是就需要Babel。执行下面命令:

    npm i babel-core babel-loader babel-preset-env babel-preset-react babel-plugin-transform-runtime babel-preset-stage-0 -D
    

    安装完成后,我们可以新建.babelrc文件来配置一下:

    {
      "presets": ["react", "env", "stage-0"], 
      "plugins": ["transform-runtime"],
    }
    

    我们再在webpack里配置一下babel-loader既可以做到代码转成ES5了

    module.exports = {
        module: {
            rules: [
                {
                    test:/\.(jsx|js)$/,
                    include: [ 
                        path.resolve(__dirname, 'src'), 
                        // 限定只在 src 目录下的 js/jsx 文件需要经 babel-loader 处理
                        // 通常我们需要 loader 处理的文件都是存放在 src 目录
                    ],
                    use:['babel-loader'],
                    exclude:/node_modules/ // 去除掉node_modules文件夹的js 不然node_modules也都转换了
                },
            ]
        }
    }
    

    细心的同学可以发现,我加了一个jsx,这样就可以写react代码啦···

    resolve解析

    在webpack的配置中,resolve我们常用来配置别名和省略后缀名

    module.exports = {
        resolve: {
            // 别名
            alias: {
                $: './src/jquery.js'
            },
            // 省略后缀
            extensions: ['.js', '.json', '.css']
        },
    }
    

    优化黑科技

    webpack给我们提供了很多优化的配置,接下来我们看看:

    拆分JS 抽离公共代码

    在webpack4之前,提取公共代码都是通过一个叫CommonsChunkPlugin的插件来完成的,但是webpack4.x以后内置了一个更好的配置项去完成这份工作:

    // 提取公共代码
    module.exports = {
        optimization: {
            splitChunks: {
                cacheGroups: {
                    vendor: {   // 抽离第三方插件
                        test: /node_modules/,   // 指定是node_modules下的第三方包
                        chunks: 'initial',
                        name: 'vendor',  // 打包后的文件名,任意命名    
                        // 设置优先级,防止和自定义的公共代码提取时被覆盖,不进行打包
                        priority: 10    
                    },
                    utils: { // 抽离自己写的公共代码,utils这个名字可以随意起 (css/js公用的都会单独抽离出来生成一个单独的文件)
                       chunks: 'initial',
                       name: 'utils',  // 任意命名
                       minSize: 0    // 只要超出0字节就生成一个新包
                    }
                }
            }
        },  
    }
    

    备注信息代码已注释的很详细,这里就不多解释了,

    • vendor 开发中我们经常用到vue.min.js、reacr.min.js ....等很多源码库,因为我们每次发布的时候变得只是业务逻辑,源码是不变的,所以我们单独抽离出来,这样也有利于浏览器缓存,提高页面加载速度,增加用户体验。
    • utils 这个配置项指的是平常开发中,我们经常会写一些公共的util,配置此项也可以单独打包出来,但是我不建议这么做,因为本身util一般也不会很大,没有这个必要。

    HappyPack

    HappyPack 允许 Webpack 使用 Node 多线程进行构建来提升构建的速度。
    HappyPack支持对css loader 和 babel loader的加速。对url-loader 和 file-loader 的支持度的问题
    首先安装:

    npm i -D happypack
    

    接下来我们加入配置:(为了方便观看,只显示当前配置项)

    const HappyPack = require('happypack'); // 多线程构建
    const happyThreadPool = HappyPack.ThreadPool({ size: 5 });  // 构造出共享进程池,进程池中包含5个子进程
    module.exports = {
        module: {
            rules: [
                {
                    test:/\.(jsx|js)$/,
                    // 开启多线程进行构建 匹配下面注释的插件
                    use:['happypack/loader?id=js'], 
                    exclude:/node_modules/ 
                },
            ]
        },   
        plugins: [
            // 多线程构建 匹配上面的loader
            new HappyPack({
                id: 'js',
                loaders: ['babel-loader'],
                threadPool: happyThreadPool, // 使用共享进程池中的子进程去处理任务
            }),
        ]
    }
    

    其实很简单,我们看奥下面的happypack插件里面的id去匹配上面loader等于js的配置

    • threadPool 开启5个子进程去处理任务

    minimizer

    在production模式,该配置会默认为我们压缩混淆代码,但这显然满足不了我们对于优化代码的诉求。下面是最优化配置:

     optimization: {
        minimizer: [
           // 自定义js优化配置,将会覆盖默认配置 最大化压缩成js
            new UglifyJsPlugin({
                exclude: /\.min\.js$/, // 过滤掉以".min.js"结尾的文件,我们认为这个后缀本身就是已经压缩好的代码,没必要进行二次压缩
                cache: true,
                parallel: true, // 开启并行压缩,充分利用cpu
                sourceMap: false,
                extractComments: false, // 移除注释
                uglifyOptions: {
                  compress: {
                    unused: true,
                    warnings: false,
                    drop_debugger: true
                  },
                  output: {
                    comments: false
                  }
                }
            }),
            // 用于优化css文件 最大化压缩css 并且去掉注释掉的css
            new OptimizeCssAssetsPlugin({
                assetNameRegExp: /\.css$/g,
                cssProcessorOptions: {
                  safe: true,
                  autoprefixer: { disable: true },
                  mergeLonghand: false,
                  discardComments: {
                    removeAll: true // 移除注释
                  }
                },
                canPrint: true
            })
        ],
    }
    

    最后

    最终代码

    const path = require('path');
    const webpack = require('webpack');
    // 插件都是一个类,所以我们命名的时候尽量用大写开头
    const HtmlWebpackPlugin = require('html-webpack-plugin'); //打包html
    const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 提取出来css
    const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); // 压缩打包后的js 
    const HappyPack = require('happypack'); // 多线程构建
    const happyThreadPool = HappyPack.ThreadPool({ size: 5 });  // 构造出共享进程池,进程池中包含5个子进程
    const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')// 最大化压缩css
    
    console.log('process.env.NODE_ENV------->',  process.env.NODE_ENV)
    // 解决css 分离后图片引入路径不正确问题
    if (process.env.type == 'build') { // 判断package.json里面是build还是dev命令
        // 开发
        var website ={
            publicPath:"/"
        }
    } else {
        // 生产
        var website ={
            publicPath:"/"
        }
    }
    
    
    module.exports = {
        // devtool:'eval-source-map',
        mode: 'development', // 模式配置
        entry: {
            main: './src/index.js',
        },             
        output: {
            filename: 'bundle.[chunkhash:6].js',
            path: path.resolve(__dirname, 'dist'),
            publicPath: website.publicPath, // 解决css 分离后图片引入路径不正确问题
        },             
        module: {
            rules: [
                {
                    test: /\.css/,
                    exclude: /node_modules/,
                    use: ['style-loader', MiniCssExtractPlugin.loader, 'css-loader'],
                },
                {
                    test: /\.less$/,
                    exclude: /node_modules/,
                    use: ['style-loader', MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'],
                },
                {
                    test: /\.(png|jpe?g|gif|svg)$/,
                    use: [
                        {
                            loader: 'url-loader',
                            options: {
                                limit: 10000,
                                name: '[name]_[hash:7].[ext]', 
                                outputPath:'static/images/'
                            }
                        }
                    ]
                },
                {
                    test: /\.(htm|html)$/,
                    use: 'html-withimg-loader'
                },
                // babel 解析es7 es6 jsx
                {
                    test:/\.(jsx|js)$/,
                    include: [ 
                        path.resolve(__dirname, 'src'),
                    ],
                    use:['babel-loader'],
                    /*
                        如果开启多线程进行构建
                        use:['happypack/loader?id=js'], 
                        loader这样写 匹配下面注释的插件
                    */
                    exclude:/node_modules/
                },
            ]
        },              
        plugins: [
            // 打包html
            new HtmlWebpackPlugin({
                template: './src/index.html',
                hash: true,
                minify: {
                    minifyCSS: true,
                    minifyJS: true, 
                    removeAttributeQuotes: true
                },
            }),
            new MiniCssExtractPlugin({
                filename: "static/css/[name].[chunkhash:8].css",
                chunkFilename: "[id].css"
            }),
            new UglifyJsPlugin({
                parallel: true, 
            }),
            new webpack.DefinePlugin({
                NODE_ENV: JSON.stringify('DEV')
            }),
            // 多线程构建 匹配上面的loader
            // new HappyPack({
            //     id: 'js',
            //     //threads: 4,
            //     loaders: ['babel-loader'],
            //     threadPool: happyThreadPool, // 使用共享进程池中的子进程去处理任务
            // }),
        ],   
        // 提取公共代码
        optimization: {
            minimizer: [
               // 自定义js优化配置,将会覆盖默认配置 最大化压缩成js
                new UglifyJsPlugin({
                    exclude: /\.min\.js$/, // 过滤掉以".min.js"结尾的文件,我们认为这个后缀本身就是已经压缩好的代码,没必要进行二次压缩
                    cache: true,
                    parallel: true, // 开启并行压缩,充分利用cpu
                    sourceMap: false,
                    extractComments: false, // 移除注释
                    uglifyOptions: {
                      compress: {
                        unused: true,
                        warnings: false,
                        drop_debugger: true
                      },
                      output: {
                        comments: false
                      }
                    }
                }),
                // 用于优化css文件 最大化压缩成css 并且去掉注释掉的css
                new OptimizeCssAssetsPlugin({
                    assetNameRegExp: /\.css$/g,
                    cssProcessorOptions: {
                      safe: true,
                      autoprefixer: { disable: true },
                      mergeLonghand: false,
                      discardComments: {
                        removeAll: true // 移除注释
                      }
                    },
                    canPrint: true
                })
            ],
            splitChunks: {
                cacheGroups: {
                    vendor: {   // 抽离第三方插件
                        test: /node_modules/,   // 指定是node_modules下的第三方包
                        chunks: 'initial',
                        name: 'vendor',  // 打包后的文件名,任意命名    
                        // 设置优先级,防止和自定义的公共代码提取时被覆盖,不进行打包
                        priority: 10    
                    },
                    // utils: { // 抽离自己写的公共代码,utils这个名字可以随意起 (css/js公用的都会单独抽离出来生成一个单独的文件)
                    //     chunks: 'initial',
                    //     name: 'utils',  // 任意命名
                    //     minSize: 0    // 只要超出0字节就生成一个新包
                    // }
                }
            }
        },        
        devServer: {
            historyApiFallback: true,
            inline: true
        },   
        // externals: {
        //     jquery: "jQuery",
        // },
        resolve: {
            // alias 别名配置,它能够将导入语句里的关键字替换成你需要的路径
            alias: {
                // 比如我们就可以直接写 import Nav from '@/Nav'
                '@': './app/component'
            },
            // 省略后缀
            extensions: ['.js', '.jsx', '.less', '.json', '.css'],
        },     
        performance: {
            hints: false // 选项可以控制 webpack 如何通知「资源(asset)和入口起点超过指定文件限制」
        }
    }
    
    同学你都读到这里了,还不点赞加关注吗?

    webpack其实还有很多优化项,很多plugins And loader等着我们去挖掘.
    小编也是萌新一枚,哪里写的不好,提出意见!我也会不断更新文章的...请大家持续关注!
    辛苦大家了.......下班回家了............

    相关文章

      网友评论

          本文标题:Webpack 4.x 小白成神之路

          本文链接:https://www.haomeiwen.com/subject/txmorftx.html