webpack

119 阅读6分钟

webpck

  • 使用webpack需要先下载 webpackwebpack-cli
  • webpck可以处理jsjson资源

命令打包

webpck 文件路径 -o 输出路径 --moode=production
webpck 文件路径 -o 输出路径 --moode=development

配置打包

  • 需要创建webpck.config.js
  • 如果需要使用dll,需要创建webpck.dll.js,不用重复打包dll
  • 项目中使用的是ES6 import export
  • webpack中使用的是nodeJs commonJs require module.exports
// webpack.config.js

// resolve 用来拼接绝对路径
const { resolve } = require("path");
const fs = require('fs');
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin");
const WorkboxWebpackPlugin = require("workbox-webpack-plugin");
const webpack = require("webpack");
const AddAssetHtmlWebpackPlugin = require("add-asset-html-webpack-plugin");

const dll = path.resolve(__dirname, 'dll');
const files = fs.readdirSync(dll);
if (files && files.length) {
    files.forEach((file) => {
        if (/.*\.manifest.json/.test(file)) {
            plugins.push(
                // 告诉webpack那些库不打包,同时使用名称也用 manifest.json 中映射后的
                new webpack.DllReferencePlugin({
                    manifest: path.resolve(dll, file)
                })
            );
        }
        if (/.*\.dll.*.js/.test(file)) {
            plugins.push(
                // 因为忽略了一些库不打包,所以在 html 中自动引入该资源
                new AddAssetHtmlWebpackPlugin({
                    filepath: path.resolve(dll, file)
                })
            );
        }
    });
}

// 公用 css loader
const commonCssLoader = [
    // 使用需下载 style-loader 包
    // 创建 style 标签,将 js 中样式字符串插入,添加到 head 中生效
    // 问题:由于构建后css是在js中,并且js体积变大引入较慢,会闪屏
    // "style-loader",
    // 解决:将构建后的 css 提取出来
    MiniCssExtractPlugin.loader,

    // 使用需下载 css-loader 包
    // 将 css 文件转成 commonJs 模块加载到js中,内容是样式字符串
    "css-loader",

    // 使用需下载 postcss-loader postcss-preset-env 包
    // css 兼容性处理
    // "postcss-loader",
    {
        loader: "postcss-loader",
        options: {
            ident: "postcss",
            plugins: () => [
                // 帮 postcss 去 package.json 中找 browserslist 配置,通过配置加载指定的css兼容性样式
                // browserslist 看下一个代码块
                require("postcss-preset-env")()
            ]
        }
    }
]

module.exports = {
    // 代码分割 方法1:单页面->单入口 多页面->多入口,需要经常修改,太笨重
    // 代码分割 方法2: optimization.splitChunks
    // 代码分割 方法3: 使用 import.then 语法动态导入,能叫该文件单独打包
    //    import(/* webpackChunkName: "test" */"./test")
    //        .then({ add } => {})
    // entry: "./src/index.js",
    entry: {
        main: "./src/index.js",
        test: "./src/test.js"
    }
    output: {
        // name 取入口的文件名 main、test
        filename: "js/[name].[contenthash:10].js",
        // __dirname 当前文件的目录的绝对路径
        path: resolve(__dirname, "build")
    },
    module: {
        rules: [
            {
                // js 语法检查
                // 检查规则在 package.json 中 eslintConfig 配置
                // eslintConfig 看下一个代码块
                // 项目代码中可以使用 // eslint-disable 等注释不对某行某文件不进行检查
                test: /\.js$/,
                exclude: /node_modules/,
                // 优先执行,这个执行完后才执行 js 兼容性
                enforce: "pre",
                loader: "eslint-loader",
                options: {
                    // 自动修复 eslint 错误
                    fix: true
                }
            },
            // 以下 loader 只会匹配一个,不能有多个配置处理同一种类型文件
            oneOf: [
                {
                    // js 兼容性,ES6 -> ES5
                    // 使用需下载 babel-loader
                    test: /\.js$/,
                    exclude: /node_modules/,
                    use: [
                        {
                            // 开启多线程打包
                            loader: "thread-loader",
                            options: { 
                                // 开启2个进程,如果不设置默认就是cpu进程-1
                                workers: 2
                            }
                        },
                        // === options.cacheDirectory = true,babel缓存
                        "babel-loader?cacheDirectory"
                    ]
                },
                {
                    test: /\.css$/,
                    user: [...commonCssLoader, "css-loader"]
                },
                {
                    test: /\.css$/,
                    use: [
                        [...commonCssLoader],

                        // 使用需下载 less less-loader 包
                        // 将 less 文件编译成 css 文件
                        "less-loader"
                    ]
                },
                {
                    // 问题:默认处理不了 html 中的图片
                    test: /\.(jpg|png|gif)$/,
                    // 使用需下载 file-loader url-loader,因为 url-loader 依赖 file-loader
                    // 使用一个 loader 不用写 use 数组
                    loader: "url-loader",
                    options: {
                        // 当图片小于8KB,就会base64处理
                        // 优点减少请求数量(减轻服务器压力),缺点文件体积会变大(请求速度变慢)
                        limit: 8 * 1024,

                        // 重命名图片名称
                        // [hash:10] 取十位 hash 值,ext 文件原来扩展名
                        name: "[hash:10].[ext]",

                        // 问题:html-loader 引入后的是 commonJs 模块, url-loader 默认使用 ES6模块解析
                        // 解析时会出现 [object Module]
                        // 解决:关闭 url-loader 的ES6模块化,使用 commonJs 解析
                        esModule: false,

                        // 修改构建后的图片路径
                        outputPath: "imgs"
                    } 
                },
                {
                    test: /\.html$/,
                    // 使用需下载 html-loader
                    // 处理 html 中的图片(负责引入img,从而给 url-loader 处理)
                    loader: "html-loader"
                },
                {
                    // 除了css、js、html、图片外的其他资源
                    exclude: /\.(css|less|js|html|jpg|png|gif)$/,
                    loader: "file-loader",
                    options: {
                        name: "[hash:10].[ext]",
                        outputPath: "media"
                    }
                }
            ]
        ]
    
    },
    plugins: [
        // 复制 "./src/index.html" 文件,自动引入打包输出后的所有资源
        new HtmlWebpackPlugin({
            template: "./src/index.html",
            // 压缩 html
            minify: {
                // 折叠空白区域
                collapseWhitespace: true,
                // 移除注释
                removeComments: true,
                userShortDoctype: true,
                // 移除空元素
                removeEmptyElements: true,
                // 移除空属性
                removeEmptyAttributes: true,
                // 压缩文内 css
                minifyCSS: true,
                // 压缩文内 js
                minifyJS: true,
                // 压缩文内 网址
                minifyURLs: true,
            }
        }),
        new MiniCssExtractPlugin({
            // 对输出的 css 指定目录重新命名
            filename: "css/built.[contenthash:10].css"
        }),
        // 压缩 css
        new OptimizeCssAssetsWebpackPlugin(),
        // 压缩 js,如果只是单纯压缩代码也可以使用 mode:production
        new TerserWebpackPlugin({
            // 启用文件缓存
            cache: true,
            // 使用多进程并行执行,提升构建效率
            parallel: true,
            // 将错误信息位置映射到模块
            sourceMap: true,
            terserOptions: {
                // 打包时剔除 console.log
                drop_console: true,
                // 打包时剔除 debugger
                drop_debugger: true
            }
        }),
        // js HMR 功能
        new webpack.HotModuleReplacementPlugin(),
        // PWA 离线可访问
        // 帮助 serviceWorker 快速启动,删除旧的 serviceWorker,生成一个 service-worker.js 配置文件
        // 需要在入口文件处理兼容性并注册serviceWorker
        // if("serviceWorker" in navigator){
        //    window.addEventListener("load", () => {
        //      navigator.serviceWorker
        //        .register("/service-worker.js")
        //        .then(() => {})
        //        .catch(() => {})
        //    })     
        // }
        // 下一次访问该服务器资源,就会从浏览器的 serviceWorker 读取上次的资源
        new WorkboxWebpackPlugin.GenerateSW({
            clientsClaim: true,
            skiipWaiting: true
        }),
    ],
    // mode: "prodution",
    mode: "development",
    
    // 开发服务器,自动编译、自动打开浏览器、自动刷新浏览器
    // 特点:在内存中编译打包,不会有输出
    // 使用需下载 webpack-dev-server
    // 启动指令: 本地安装 npx webpack-dev-server、全局安装 webpack-dev-server
    devServer: {
        // 运行的项目的目录(构建后的)
        contentBase: resolve(__dirname, "./build"),
        // 启动 Gzip 压缩
        compress: true,
        // 端口
        port: 3000,
        // 自动打开浏览器
        open: true,
        // 开启 HMR 功能,如果修改了webpack配置,必须重启服务
        // 问题:只要修改了一个模块,所有模块都会重新编译打包
        // 解决: 使用HMR(只更新修改的模块)
        // 样式文件:
        //     devServer.hot 能生效;
        //     原因是 style-loader 内部实现,所以开发环境使用 style-loader,而不把 css 抽取出来;
        // js文件:
        //     默认不生效;
        //     需要添加插件 webpack.HotModuleReplacementPlugin
        // html文件:
        //     默认不生效;
        //     需要将 entry 修改为 ["./src/index.js", "./src/index.html"],将 html 引入;
        //     修改会导致所有都更新,但不用处理因为就只有一个 html 文件;
        hot: true
    },
    // 1. 不管单入口还是多入口,会将 node_modules 中代码单独打包成一个 chunk 输出
    // 2. 自动分析多入口 chunk 是否有公共文件,如果有也会打包成单独chunk
    optimization: {
        splitChunks: {
            chunks: "all"
        }
    },
    // 不打包第三方包,在入口文件使用 cdn 引入
    externals: {
        // 忽略库名: npm包名
        jquery: "jQuery"
    }
}
// package.json 中 browserslist 配置
// 默认会使用 production ,与 webpack.config.js 中的 mode 无关,与 process.env.NODE_ENV 有关

browserslist: {
    "development": [
        // 兼容最新版本谷歌浏览器
        "last chrome version",
    ],
    "produciton": [
        // 兼容99.8%的浏览器 && 不要没人用的浏览器
        ">0.2%",
        "not dead",
        "not op_mini all"
    ]
},
eslintConfig: {
    // 这里是使用的airbnb-base规则,可以使用别的规则
    // 使用需下载 eslint-config-airbnb-base eslint-plugin-import eslint 包
    "extend": "airbnb-base",
    "env": {
        // 支持浏览器的 window navigator 等变量
        "browser": true,
        // 支持 node 的变量
        "node": true
    }
}
// webpack.dll.js
const { resolve } = require("path");
const webpack = require("webpack");
const venders = [
    'axios',
    'decimal.js',
    'immutable',
    'lodash',
    'moment',
];

module.exports = {
    mode: "production",
    entry: {
        venter: venders
    },
    output: {
        filename: "[name].js",
        path: resolve(__dirname, "dll"),
        // 构建后的包里往外抛出的内容名称,类似 export jquery_12345
        library:"[name]_[hash:5]"
    },
    plugins: [
        // 映射构建后的包内容
        new webpack.DllPlugin({
            name: "[name]_[hash:5]",
            path: resolve(__dirname, "dll/manifest.json")
        })
    ]
}

懒加载 & 预加载

// 1. 将引入放到异步中就是懒加载,使用的时候才加载
// 2. 加上 webpackPrefetch 就是预加载,还未使用就加载了,使用的时候就是使用的缓存
//    预加载与正常加载的区别,预加载是等其他资源都加载完毕,浏览器空闲后才加载,但是兼容性不太好
document.getElement("btn").onclick = function() {
    import(/* webpackChunkName: "test", webpackPrefetch: true */"./test").then({ add } => {})
}

优化

  • 开发环境

    1. 打包构建速度
    开启 HMR 热更新
    
    1. 调试
        devtool: "eval-source-map"
    
  • 生产环境

    1. 打包构建速度
    oneOf & exclude & include 
    打包体积优化
        压缩html、css、js
        小图片转base64
        合理配置hash值
        开启Gzip
        树摇(tre shaking) -> 去除无用代码
            前提:1.使用ES6、2.mode为production
            注意:package.json 设置不需要树摇的 sideEffects: ["*.css", "*.less"]
    多进程打包
    externals
    dll
    
    
    1. 运行性能
    缓存
        babel缓存 -> 让第二次打包速度更快
            cacheDirectory: true
        文件资源缓存 -> 让代码上线运行更快
            hash、chunkhash、contenthash
            // 问题:如果服务器强缓存,引用的包做修改不会重新请求
            // 解决:给文件名加上 hash 值,如果重新打包文件名称会改变就会重新请求
            // 问题:每一次webpack构建时都会生成一个唯一的hash,改了一个文件重新打包所有的文件都会更改hash,文件名就都变了
            // chunkhash 根据chunk生成的hash,但是也不行因为css是在js中被引用的,属于同一个chunk
            // contenthash 根据文件内容生成的hash,不同文件的一定不一样
    code split 代码分割
    懒加载预加载
    PWA 离线访问