webpack5+(二)

67 阅读10分钟

webpack 懒加载和预加载配置:

// webpack.config.js:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { resolve } = require('path');
const path = require('path'); // 引入 node的核心模块 path,来处理路径
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin');

// 设置 nodejs的环境变量: 决定使用 browserslist的哪个环境
// process.env.NODE_ENV = 'development';

/**
 * 缓存: 
 *  1.babel缓存: 
 *      设置选项 cacheDirectory: true;
 *      作用: 让第二次打包构建速度更快
 *  2.文件资源缓存: 
 *      hash: 每次 webpack构建时会生成一个唯一的 hash值。
 *          问题: 因为 js和 css同时使用一个 hash值,如果重新打包,会导致所有缓存失效(可能我却只改动一个文件)
 *      chunkhash: 根据 chunk生成的 hash值。如果打包来源于同一个 chunk,那么 hash值就一样。
 *          问题: js和 css的 hash值还是一样的,因为 css是在 js中被引入的,所以同属于一个 chunk
 *      contenthash: 根据文件的内容生成 hash值,不同文件 hash值一定不一样。
 *      作用: 让代码上线运行缓存更好使用
 */

/**
 * tree-shaking: 去除无用的代码
 *  前提: 1.必须使用 es6模块化; 2.开启 production环境
 *  作用: 减少代码体积
 *  在 package.json中配置:
 *      "sideEffects": false, 所有代码都没有副作用(都可以进行 tree-shaking)
 *          问题: 可能会把 css / @babel/polyfill (副作用)文件干掉
 *      "sideEffects": ["*.css", ".less"], 不对 .css, .less文件进行 tree-shaking
 */

/**
 * code split(代码分割):
 * 总共有三种方式:
 * 1.entry配置多入口(多页面应用),每个入口文件打包成为一个 chunk,所以会有多个 js文件输出;
 * 2.配置 optimization选项, 将 node_modules中代码单独打包成一个 chunk最终输出;
 * 3.在需要引入 js模块文件的文件中使用 import动态导入:
 *  // 通过 js代码, 让某个文件被单独打包成一个 chunk
 *  // import动态导入语法(es11): 能将某个文件单独打包
 *  import('./other').then(({ count }) => {
 *          // 文件加载成功
 *          console.log(count);
 *      } 
 *  ).catch(() => {
 *          console.log('文件加载失败');
 *      }
 *  )
 * 作用: 可以拆分为多个 js文件,从而实现多文件并行加载,速度更快
 */

/**
 * 懒加载和预加载(js文件):
 *  懒加载: 可以理解为延迟加载,触发某些条件的时候才能加载,而不是一开始就加载
 *      1.当文件需要时才加载
 *      document.getElementById('btn').onclick = function() {
 *          import('./lazy').then(({ lazyVal }) => {
 *              console.log('点击了');
 *              console.log(lazyVal);
 *          });
 *      }
 *  预加载 prefetch: 会在使用之前,提前加载 js文件
 *      1.预加载会等其他资源加载完毕,浏览器空闲了,再偷偷加载资源
 *      缺点: 预加载兼容性非常差,只能在PC端高版本浏览器中使用,在移动端或ie浏览器中有相当大兼容性问题,故慎用
 *      document.getElementById('btn').onclick = function() {
 *          import('./lazy').then(({ lazyVal }) => {
 *              console.log('点击了');
 *              console.log(lazyVal);
 *          });
 *      }
 *  正常加载:
 *      1.可以认为是并行加载(同一时间加载多个文件),文件越多加载越慢,而且按照引入的顺序进行加载
 */

// 复用 loader
const commonCssLoader = [
    // MiniCssExtractPlugin.loader,这个 loader取代 style-loader,提取 js中的 css成单独文件
    MiniCssExtractPlugin.loader,
    'css-loader',
    // css兼容性处理
    {
        loader: 'postcss-loader',
        options: {
            postcssOptions: {
                ident: 'postcss',
                plugins: [
                    // postcss的插件
                    // require('postcss-preset-env')(),这种写法也行
                    'postcss-preset-env'
                ]
            }
        }
    }
]

// # webpack性能优化: 1.开发环境性能优化; 2.生产环境性能优化。
// # 开发环境性能优化: 1.优化打包构建速度; 2.优化代码调试。
// # 生产环境性能优化: 1.优化打包构建速度; 2.优化代码运行的性能。
/**
 * HMR: hot module replacement 热模块替换/模块热替换
 * 作用: 一个模块发生变化,只会重新打包这一个模块(而不是打包所有模块),极大提升构建速度。
 * 样式文件: 可以使用 HMR功能,因为 style-loader内部实现了(webpack5以下版本,webpack5+默认开启 HMR)
 * js文件: 默认不能使用 HMR,需要修改 js代码,添加支持 HMR功能的代码(webpack5以下版本,webpack5+默认开启 HMR)
 *   注意: HMR功能对 js的处理,只能处理非入口 js文件的其他文件
 *   if(module.hot) {
 *      // 一旦 module.hot为 true,说明开启了 HMR功能, 让HMR功能代码生效
 *      module.hot.accept('./xxx.js', function(){
 *          // 方法会监听 xxx.js文件的变化,一旦发生变化,其他模块不会重新打包构建
 *          //  会执行后面的回调函数(相关操作)
 *          xxx();
 *      })
 *   }
 * html文件: 默认不能使用 HMR功能,同时会导致问题,html文件不能热更新了(webpack5以下版本,webpack5+默认开启 HMR)
 *     解决: 修改 entry入口,将html文件引入
 */

/**
 * 作用: 指示 webpack所做的事情(当你运行 webpack的命令时,会加载里面的配置);
 * 所有构建工具都是基于 nodejs平台运行的,故模块化规范采用 commonjs。
 * loader: 1.下载;2.使用(配置loader); plugins: 1.下载;2.引入;3.使用。
 * 运行项目指令:  1.npx webpack 或 npm run test 会将打包结果输出; 
 *               2.npx webpack serve 只会在内存中编译打包,没有输出。
 */
module.exports = {
    context: path.resolve(__dirname, './'),
    // 必填,webpack执行构建入口起点(打包入口文件路径)
    // entry: './src/index.js', 
    // webpack执行构建入口起点,同时引入 html文件,这样修改 html文件,
    // 该 html文件才会热更新(webpack5以下版本,webpack5+不需要这样配置,上面那种entry配置即可)
    entry: ['./src/index.js', './example/index.html'], 
    // 多入口(多页面应用)
    // entry: {
    //     index: './src/index.js', 
    //     other: './src/other.js'
    // }, 
    // 输出
    output: {
        // 将所有依赖的模块合并输出到 built.js(输出文件名)
        filename: 'js/built.[contenthash:10].js', 
        // path 打包出口路径(输出文件的存放路径,必须是绝对路径)
        // __dirname nodejs的变量,代表当前文件的目录绝对路径
        path: path.resolve(__dirname, './dist'), 
    },
    // loader的配置
    module: {
        rules: [
            /** 
             * 语法检查: eslint-loader eslint
             * 注意: 只检查自己写的源代码,第三方的库是不用检查的
             * 设置检查规则: package.json中 eslintConfig中设置~
             * "eslintConfig": {
             *     "extends": "airbnb-base"
             * }
             * airbnb --> eslint-config-airbnb-base eslint-plugin-import eslint
             * npm install -D eslint-loader eslint eslint-config-airbnb-base eslint-plugin-import
             * 正常来讲,一个文件只能被一个 loader处理,当一个文件要被多个 loader处理,那么一定要指定 loader执
             * 行的先后顺序: 先执行 eslint,再执行 babel
             */
            // {
            //     test: /\.js$/,
            //     exclude: /node_modules/,
            //     // 优先执行
            //     enforce: 'pre',
            //     loader: 'eslint-loader',
            //     options: {
            //         // 自动修复 eslint的错误
            //         fix: true
            //     }
            // },
            {
                // 以下 loader只会匹配一个,提升构建速度(同一个文件不会被多个 loader过一遍, 如果匹配一个,后面的 loader就会被忽略)
                // 注意: 不能有两个配置处理同一种类型文件,如果确实有这种情况,可只保留一个,
                // 其他的 loader提取到外面 rules中,如上面的 eslint-loader
                oneOf: [
                    // 详细 loader配置
                    {
                        // 匹配哪些文件
                        test: /\.css$/,
                        // 使用哪些 loader进行处理
                        use: [
                            // use数组中loader执行顺序: 从右到左,从下到上(即倒序)依次执行
                            // 创建 style标签,将 js中的样式资源插入进行,添加到 html文件的 head中生效
                            // 'style-loader',
                            ...commonCssLoader
                        ]
                    },
                    {
                        // 处理 stylus资源
                        test: /\.stylus$/,
                        // 使用多个 loader的时候用 use
                        use: [
                            // 'style-loader',
                            ...commonCssLoader,
                            // 将 stylus文件编译成 css文件
                            'stylus-loader'
                        ]
                    },
                    // webpack 5默认会打包图片资源,添加下面配置反而无法显示,webpack5+中已经废弃 url-loader
                    // {
                    //     // 处理图片资源
                    //     test: /\.(jpg|png|gif)$/,
                    //     // 使用一个 loader
                    //     // 下载 url-loader file-loader(file-loader依赖 url-loader)
                    //     loader: 'url-loader',
                    //     options: {
                    //         // 图片大小小于 8kb,就会被 base64处理
                    //         // 优点: 减少请求数量(减轻服务器压力)
                    //         // 缺点: 图片体积会更大(文件请求速度更慢)
                    //         limit: 8 * 1024,
                    //         // 问题: 因为 url-loader 默认使用 es6模块化解析,而 html-loader 引入图片是 commonjs,解析时
                    //         // 会出现问题 [object Module]
                    //         // 解决: 关闭 url-loader的 es6模块化,使用 commonjs解析
                    //         esModule: false,
                    //         // 默认文件名是一段哈希值+文件名的后缀
                    //         // 给图片进行重命名
                    //         // [hash:10]取图片的 hash的前 10位
                    //         // [name]取文件原始名
                    //         // [ext]取文件原来拓展名
                    //         name: '[hash:10].[ext]'
                    //     }
                    // },
                    // 处理图片资源(webpack5+)
                    {
                        test: /\.(jpg|png|gif)$/,
                        // 类型为资源模块类型, assets-module(webpack5+)
                        type: 'asset',
                        // 解析
                        parser: {
                            // 转base64的条件
                            dataUrlCondition: {
                                maxSize: 25 * 1024, // 25kb
                            }
                        },
                        generator: {
                            filename: 'img/[name]_[hash:8][ext]',
                            // 打包后对资源的引入, 已经有文件目录 img了
                            publicPath:'./'
                        }
                    },
                    {
                        // 处理 html中 img资源
                        test: /\.html$/,
                        // 处理 html文件的 img图片(负责引入 img,从而能被 url-loader进行处理)
                        loader: 'html-loader',
                    },
                    // 打包其他资源(除了 html/js/css资源以外的资源)
                    {
                        // 排除 css/less/js/html资源
                        exclude: /\.(jpg|css|less|stylus|js|json|html)$/,
                        loader: 'file-loader',
                        options: {
                            name: '[hash:10].[ext]',
                            // 输出到指定的目录
                            outputPath: 'media',
                        }
                    },
                    /**
                     * js兼容性处理: babel-loader @babel/core @babel/preset-env
                     * npm install -D babel-loader @babel/core @babel/preset-env
                     * 1.基本兼容性处理: @babel/preset-env;
                     *   问题: 只能转换基本语法,如 promise不能转换
                     * 2.全部 js兼容性处理: @babel/polyfill(babel 7.4.0开始,这个包已经被废弃);
                     *   npm install -D @babel/polyfill
                     *   源代码中引入即可, import '@babel/polyfill'
                     *   问题: 我只要解决部分兼容性问题,但是它会将所有兼容性代码全部引入,导致编译后代码体积太大了
                     * 3.需要做兼容性处理的就做: 按需加载, core-js
                     *   npm i -D core-js
                     *   注意: 使用这种方式时, 第2种方式不能再使用
                     */
                    {
                        test: /\.js$/,
                        exclude: /node_modules/,
                        loader: 'babel-loader',
                        options: {
                            /**
                             * 对应上面第1种方式
                             */
                            // 预设: 指示 babel做怎样的兼容性处理
                            // presets: ['@babel/preset-env']
                            /**
                             * 对应上面第3种方式
                             */
                            // 预设: 指示 babel做怎样的兼容性处理
                            presets: [
                                [
                                    '@babel/preset-env',
                                    {
                                        // 按需加载
                                        useBuiltIns: 'usage',
                                        // 指定 core-js版本
                                        corejs: {
                                            version: 3
                                        },
                                        // 指定兼容性做到哪个版本浏览器
                                        targets: {
                                            // 兼容版本为 60及以上
                                            chrome: '60',
                                            firefox: '50',
                                            ie: '9',
                                            safari: '10',
                                            edge: '17',
                                        } 
                                    }
                                ]
                            ],
                            // 开启 babel缓存
                            // 第二次构建的时候,会读取之前的缓存,从而构建速度更快
                            cacheDirectory: true
                        }
                    }
                ]
            }
        ]
    },
    // 模式
    // 生产环境下会自动压缩 js代码
    mode: 'production', // 生产模式
    // mode: 'development', // 开发模式
    // 开发服务器 devServer: 用来自动化(自动编译,自动打开浏览器,自动刷新浏览器~~)
    // 特点: 只会在内存中编译打包,不会有任何输出
    // 安装 webpack-dev-server: npm install -D webpack-dev-server
    // 启动 devServer命令为: npx webpack serve(webpack5以下版本,启动指令为 npx webpack-dev-server)
    devServer: {
        // 项目构建后路径(webpack5+ 不支持,已被弃用)
        // contentBase: resolve(__dirname, 'dist'),
        // 项目构建后路径(webpack5+)
        static: resolve(__dirname, 'dist'),
        // 启动 gzip压缩
        compress: true,
        // 端口号
        port: 3000,
        // 自动打开浏览器
        open: true,
        // 开启 HMR功能(webpack5以下版本支持,webpack5+设置这个选项值无效,webpack5+默认热更新)
        // hot: true,
        // 监听 index.html文件变化(webpack5+ 中新增的配置选项,
        // 如果不添加就无法监听 index.html文件变化,webpack5以下版本不用添加配置就能监听)
        watchFiles: ['./example/index.html']
    },
    // plugins的配置
    plugins: [
        // html-webpack-plugin
        // 功能: 默认会创建一个空的 html,自动引入输出的所有资源(js/css)
        // 需求: 需要有结构的html文件
        new HtmlWebpackPlugin({
            // 将'./example/index.html'文件作为模板,并自动引入打包输出的所有资源(js/css)
            template: './example/index.html',
            // 压缩 html代码
            minify: {
                // 移除空格
                collapseWhitespace: true,
                // 移除注释
                removeComments: true
            }
        }),
        new MiniCssExtractPlugin({
            // 对输出的 css文件进行重命名
            filename: 'css/built.[contenthash:10].css',
        }),
        // 压缩 css(webpack5以前版本,目前webpack5+仍支持,之后会废弃)
        // new OptimizeCssAssetsWebpackPlugin()
        // 压缩 css(webpack5+)
        new CssMinimizerWebpackPlugin()
    ],
    // 构建目标,默认为 'web',编译为 web环境来加载 chunk
    // target有想、两个选项值, 'web'与'node'
    target: 'web',
    /**
     * source-map: 是一种提供源代码到构建后代码映射的技术(如果构建后代码出错了,通
     * 过映射可以追踪源代码错误)
     * 通常可以[inline- | hidden- | eval-][nosources-][cheap-[module-]]source-map的方式组合
     * source-map: 外部 source-map
     *  1.追踪到错误代码的准确信息和源代码的错误位置
     * inline-source-map: 内联 source-map
     *  1.只生成一个内联 source-map; 2.错误代码的准确信息和源代码的错误位置
     * hidden-source-map: 外部 source-map
     *  1.只隐藏源代码,不隐藏构建后代码; 2.追踪到错误代码错误,但是没有错误位置; 3.不能追踪源代码错误,只能提示到构建后代码的错误位置
     * eval-source-map: 内联 source-map
     *  1.每一个文件都生成对应的 source-map,都在 eval函数中; 2.追踪到错误代码的准确信息和源代码的错误位置
     * nosources-source-map: 外部 source-map
     *  1.代码全部隐藏(包括源代码和构建后代码); 2.追踪到错误代码准确信息,但是没有任何源代码信息; 
     * cheap-source-map: 外部 source-map
     *  1.追踪到错误代码的准确信息和源代码的错误位置; 2.只能精确到行,不能精确到某一条语句
     * cheap-module-source-map: 外部 source-map
     *  1.追踪到错误代码的准确信息和源代码的错误位置
     * 内联 source-map与 外部 source-map区别: 1.外部 source-map生成了单独的文件,内联没有; 2.内联构建速度更快
     * 开发环境: 一般需要速速快,调试更友好的这种配置
     *   速度快(eval > inline > cheap > ...): eval-cheap-source-map, eval-source-map
     *   调试更友好: source-map, cheap-module-source-map, cheap-source-map
     *   故综合考虑的配置一般为: eval-source-map(vue,react脚手架默认 source-map) / eval-cheap-module-source-map
     * 生产环境: 一般需要考虑源代码要不要隐藏,调试要不要更友好(内联会让代码体积变大,所以在生产环境一般不用内联)
     *   源代码隐藏: nosources-source-map(全部隐藏), hidden-source-map(只隐藏源代码,会提示构建后代码错误)
     *   调试友好: source-map, cheap-source-map, cheap-module-source-map, hidden-source-map, nosources-source-map
     *   故综合考虑的配置一般为: source-map / cheap-module-source-map
     */
    devtool: 'source-map',
    /**
     * 1.可以将 node_modules中代码单独打包一个 chunk最终输出;
     * 2.如果是多入口,自动分析多入口 chunk中, 有没有公共的文件,如果有会打包成单独一个 chunk,而不会在引用它的文件中重复打包多次。
     */
    optimization: {
        splitChunks: {
            chunks: 'all'
        }
    }
};

/**

  • 使用 dll技术,对某些库(第三方库: jquery、react、vue)进行单独打包
  • 打包指令: npx webpack --config webpack.dll.js */