Webpack4&5学习笔记

1 Webpack4

1.1 五大核心概念

  1. 【入口(entry)】:指示webpack应该使用哪个模块来作为构建其内部依赖图的开始。

    1. string"./src/index.js"

      单入口,打包形成一个chunk,输出一个bundle文件,输出的文件名默认叫main.js

    2. array["./src/index.js", "./src/index.html"](dll就是此写法)

      多入口,打包形成一个chunk,输出一个bundle文件,一般用于HTML文件的HMR功能。

    3. object{index: "./src/index.js", add: "./src.add.js"}

      多入口,有多少个入口文件就形成几个chunk,输出多少个bundle文件,文件名称是对象中的key

    4. 特殊用法:

    {
        index: ["./src/index.js", "./src/count.js"],
        add: "./src/add.js"
    }
    
  2. 【输出(output)】:在哪里输出文件,以及如何命名这些文件。

    output:{
        filename: "js/[name].js",
        path: resolve(__dirname, "build"), // 输出文件目录(将来所有资源输出的公共目录)
        publicPath: '/', // 所有资源引入的公共路径前缀,一般用于生产环境下,所有引入的资源都加入此前缀
        chunkFilename: "js/[name]_chunk.js", // 打包其他资源时使用此命名,若不配置此项,则使用filename命名
        library: "[name]", // 一般用于dll打包所向外暴露的变量名
        libraryTarget: "window/global/commonjs" // 向外暴露的变量名挂载到哪个全局变量
    }
    
  3. 【loader】:处理那些非js文件(webpack自身只能解析js和json)。
    1)webpack本身只能处理js、json模块,若要加载其他类型的文件或者模块,就需要使用对应的loader。它本身是一个函数,接收原文件作为参数,返回转换的结果。
    2)loader一般以xxx-loader的方式命名,xxx代表了这个loader要做的转换功能,比如css-loader。

  4. 【插件(plugin)】:执行范围更广的任务,从打包到优化都可以实现。
    1)插件可以完成一些loader完成不了的功能。

  5. 【模式(mode)】:有生产模式production和开发模式development。
    1)开发依赖:帮助程序员加工代码的库,都是开发依赖。
    2)生产依赖:帮助程序员实现功能效果的库,都是生产依赖。

1.2 其他配置

1. devServer

  1. 安装webpack-dev-server

    webpack@4、webpack-cli@3、webpack-dev-server@3	这三个版本相容。
    局部安装:npm install webpack-dev-server@3 -D
    全局安装:npm install webpack-dev-server@3 -g
    
  2. 配置:与五大核心概念平级

    // webpack.config.js
    module.exports = {
        ...
        module: {},
        // 开发服务器 devServer:用来自动化(自动编译,自动打开浏览器,自动刷新浏览器~)
        // 特点:只会在内存中编译打包,不会有任何输出
        // 启动devServer的指令为:npx webpack-dev-server
        devServer:{
            contentBase: resolve(__dirname, "build"), // 运行代码的目录
            watchContentBase: true, // 监视 contentBase 目录下的所有文件,一旦文件变化就会 reload
            watchOptions: {
                ignored: /node_modules/, // 忽略文件
            }
            compress: true, // 启动gzip压缩
            port: 5000, // 端口号
            host: "localhost", // 域名
            open: true, // 自动打开浏览器
            hot: true, // 开启HMR功能
            clientLogLevel: "none", // 不要显示启动服务器日志信息
            quiet: true, // 除了一些基本启动信息外,其他内容不要显示
            overlay: false, // 若出错了,不要全屏提示
            // 服务器代理,目的是解决开发环境跨域问题
            proxy: {
                // 一旦devServer(5000)服务器接收到 /api/xxx 的请求,就会把请求转发到另外一个服务器(3000)
                "/api": {
                    target: "http://localhost:3000",
                    // 发送请求时,请求路径重写,将 /api/xxx --> /xxx(去掉/api)
                    pathRewrite: {
                        "^/api": ""
                    }
                }
            }
        }
        ...
    }
    
  3. 修改package.json中的script指令:"dev": "webpack-dev-server"

  4. 修改后运行指令:npm run dev

  5. 生产环境准备

    1. 新建config文件夹,重命名webpack.config.js为webpack.dev.js,放入config文件夹。

    2. 复制webpack.dev.js,重命名为webpack.prod.js,删除其中的devServer配置,因为这是开发环境特有的,生产环境不需要。

    3. 修改pack.json中的script指令:

      "start": "webpack-dev-server --config ./config/webpack.dev.js",
      "build": "webpack --config ./config/webpack.prod.js"
      
    4. 修改output中的path为:path: resolve(__dirname, '../build')

2. resolve

// webpack.config.js
mode: "development",
// 解析模块的规则
resolve: {
    // 配置解析模块路径别名。优点:简写路径;缺点:没有路径提示
    alias: {
        $css: resolve(__dirname, "src/css")
    },
	// 配置省略文件路径的后缀名
    extensions: [".js", ".json", ".jsx", ".css"],
	// 告诉webpack解析模块是去找哪个目录(假如现在webpack.config.js处于第三级目录)
    modules: [resolve(__dirname, "../../node_modules"), "node_modules"]
}

1.3 说明

  1. webpack.config.js:用于存储webpack的配置信息。

1. webpack打包的基本流程

  1. 连接:webpack从入口JS开始,递归查找出所有相关的模块,并连接起来形成一个网(图)的结构。
  2. 编译:将js模块中的模块化语法编译为浏览器可以直接运行的模块语法,当然其他类型的资源也会处理。
  3. 合并:将图中所有编译过的模块合并成一个或少量的几个文件,将浏览器真正运行的是打包后的文件。

2. loader和plugin的区别

  1. loader:用于加载特定类型的资源文件,webpack本身只能打包js和json。
  2. plugin:用来扩展webpack其他方面的功能,一般loader处理不了的资源、完成不了的操作交给插件处理。

3. live-reload与HMR

  1. 不同点:

    live-reload(自动刷新):刷新整体页面,从而查看到最新代码效果,页面状态全部都是最新的。

    HMR(热膜替换):没有刷新整个页面,只是加载了修改模块的打包文件并运行,从而更新页面的局部界面,整个界面的其他部分的状态还在。

  2. 相同点:代码修改后都会自动重新编译打包。

1.3 开启项目

1. 初始化项目

  1. 使用npm init 或者yarn init生成一个package.json文件

2. 安装webpack

  1. npm install webpack@4 webpack-cli@3 -g全局安装作为指令使用。
  2. npm install webpack@4 webpack-cli@3 -D本地安装,作为本地依赖使用。

3. 运行

  1. 执行webpack ./src/js/app.js -o ./build/js/app.js --mode=development/production命令即可完成打包。

4. 结论

  1. webpack能够编译打包js和json文件。
  2. 能将es6的模块化语法转换成浏览器能识别的语法,而且也能压缩代码。

5. 缺点

  1. 默认不能编译打包css、img等文件。
  2. 默认不能将js的es6基本语法转换为es5以下语法。

6. 改善

  1. 使用webpack配置文件解决,自定义功能,需要安装响应的loader和plugin。

7. webpack配置文件

  1. 规范:遵循commonJS规范,所有构建工具都是基于nodejs平台运行的。src文件则遵循ES6modeule规范。

  2. 目的:在项目根定义配置文件,通过自定义配置文件简化上述运行指定的操作。

  3. 文件名称:webpack.config.js。

  4. 文件内容:五大核心概念

    // webpack.config.js
    const {resolve} = require('path');
    module.exports = {
        mode:'development',
        entry:'./src/js/app.js',
        output:{
            // __dirname: 代表当前文件的绝对路径,build: 代表文件夹
            path: resolve(__dirname, 'build'),
            // 输出的文件名
            filename: 'built.js'
        },
        module:{
            rules:[
                // 在此处配置一个一个loader
            ]
        },
        plugin:[
            // 此处专门用于配置插件,插件必须经过实例化这一环节
        ]
    }
    
  5. 运行指令:webpack,运行了该指令后会自动找webpack.config.js文件。

2 基础配置

2.1 打包css文件

  1. 安装:npm i style-loader@2 css-loader@5 -D
// webpack.config.js
module.exports = {
    ...
    module: {
        rules: [
            {	
                // 匹配哪些文件
                test: /\.css$/,
                // 使用哪些loader处理,use数组中loader的执行顺序:从左到右,从下到上 依次执行
                use: [
                    // 在head中创建style标签并将css-loader编译后的文件进行引入
                    'style-loader',
                    // 将css文件变成commonjs模块加载到js中,里面的内容是样式字符串
                    'css-loader'
                ]
            }
        ]
    }
    ...
}

2.2 打包less文件

  1. 安装:npm i less@4 less-loader@7 -D
// webpack.config.js
module.exports = {
    ...
    module: {
        rules: [
            {	
                test: /\.less$/,
                use: [
                    'style-loader',
                    'css-loader',
                    // 将less翻译成css,less-loader依赖于less
                    'less-loader'
                ]
            }
        ]
    }
    ...
}

2.3 打包html文件

  1. 创建html文件:不要在该文件引入任何css和js文件。
  2. 打包html文件需要使用插件html-webpack-plugin
  3. 安装:npm i html-webpack-plugin@4 -D
// webpack.config.js
// 插件使用:1.下载 2.引入 3.实例化(new)
const HtmlWebPackage = require('html-webpack-plugin');
module.exports = {
    ...
	plugins: [
        // 功能:默认创建一个空的html文件,自动引入打包输出的资源(js/css)
        // 需要有结构的html的话,则需要进行配置
        new HtmlWebPackage({
            // 赋值该文件的html文件中的结构
            template: './src/index.html'
        })
    ]
    ...
}

2.4 打包图片资源

  1. 在css/less文件中通过背景图的方式引入图片。
  2. 安装:npm i file-loader@6 url-loader@4 -D
// webpack.config.js
module.exports = {
    ...
  module: {
    rules: [
      {
        test: /\.(jpg|png|gif)$/,
        // 下列写法与此写法作用一致use:[{loader:'url-loader',options:{...}}]
        // url-loader: 默认处理不了html中的img图片,url-loader依赖于file-loader
        // url-loader相对于file-loader的优势是可以对图片进行动态转换base64编码(控制limit属性值可以控制阈值)
        loader: 'url-loader',
        options: {
          // 图片大小小于 8kb,就会被base64处理;若大于 8kb 就用file-loader处理(相当于处理其他资源一样)
          // 优点:减少请求数量(减轻服务器压力); 缺点:图片体积会更大(文件请求速度变慢)
          limit: 8 * 1024,
          // 打包后的文件命名取前10位hash值
          name: '[hash:10].[ext]',
          // 若打包后html中的图片的src='[object Module]'
          // 解决办法:url-loader中加入一个配置 -> esModule: false即可
          esModule: true
        }
      },
    ]
  },
    ...
}
  1. 打包html文件中的img图片
  2. 安装:npm i html-loader@1 -D
module: {
    rules: [
        {	
            test: /\.html$/,
            // 处理html文件中的img图片资源,主要负责引入img,从而能被url-loader进行处理
            use: ['html-loader']
            // 若打包后html中的图片的src='[object Module]',因为html-loader引入的图片是commonjs语法
            // 解决办法:url-loader中加入一个配置 -> esModule: false即可
        }
    ]
},

2.5 打包其他资源

  1. 其他资源(字体、音视频等)webpack不能解析,需要借助loader编译解析。
module: {
    rules: [
      {
        // 要排除的文件
        exclude: /\.(html|js|css|less|jpg|png|gif)$/,
        // 用于处理其他资源,也可以处理图片资源,核心操作:提取资源到指定位置,且可以修改文件名等操作
        loader: 'file-loader',
        options: {
          name: '[hash:10].[ext]',
          // 输出路径
          outputPath: '/assets/font'
        }
      }
    ]
},

2.6 打包css为单独文件

  1. 安装:npm i mini-css-extract-plugin@1 -D

  2. 引入:const MiniCssExtractPlugin = require('mini-css-extract-plugin')

  3. 配置:

    // webpack.config.js
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    
    module.exports = {
      module: {
        rules: [
          {
            test: /\.css$/i,
            use: [MiniCssExtractPlugin.loader, "css-loader"],
          },
        ],
      },
      plugins: [new MiniCssExtractPlugin()],
    };
    
  4. 由于提取了独立的文件,要从外部引入,所以可能会有路径的问题。
    解决方案:在output配置中,添加publicPath: '/',publicPath根据实际情况自行调整,若上线运行值为:/imgs,若本地右键运行值为:/build/imgs

2.7 css兼容性处理

  1. 安装:npm i postcss@8 postcss-loader@4 postcss-preset-env@7 -D

  2. postcss-preset-env:帮助postcss识别某些环境,从而加载指定的配置,精确到某个浏览器的版本。运作时,帮助postcss找到package.json中的browserslist里面的配置,并通过配置加载指定的css兼容性样式。

  3. 使用顺序:["css-loader", "postcss-loader", "less-loader"]

    // webpack.config.js
    // 设置node环境变量,使得postcss加载browserslist中的"development"兼容样式
    process.env.NODE_ENV = 'development'
    module.exports = {
        module: {
            rules: ['css-loader',
                {
                    loader: 'postcss-loader',
                    options: {
                        postcssOptions:{
                            ident: 'postcss',
                            plugins: [
                                // postcss的插件
                                require('postcss-preset-env')
                            ]
                        }
                    }
                }
            ]
        }
    }
    
  4. 在package.json配置,在其中追加browserslist配置,通过配置加载指定的css兼容性样式(vue、react中的配置)

    // package.json
    "browserslist": {
        // 开发环境
        "development": [
            "last 1 chrome version", // 最新版的chrome浏览器版本
            "last 1 firefox version", // 最新版的firefox浏览器版本
            "last 1 safari version"
        ],
        // 生产环境:默认是生产环境
        "production": [
            ">0.2%", // 兼容市面上99.8%的浏览器
            "not dead", // "死去"的浏览器不做兼容,例如IE8
            "not op_mini all", // 不做opera浏览器mini版兼容
        ]
    }
    
  5. browserslist是一个单独的库,被广泛用在各种设计浏览器或者移动端的兼容支持工具中。关于browerlist更详细的配置,参考https://github.com/browserslist/browerslist

2.8 压缩css

  1. 安装:npm i optimize-css-assets-webpack-plugin@6 -D

    // webpack.config.js
    module.exports = {
        plugins: [
            new OptimizeCssAssetsWebpaclPlugin(),
        ]
    }
    

2.9 js语法检查

  1. 概述:对js基本语法错误或者隐患,进行提前检查。

  2. 安装:npm i eslint@7 eslint-loader@4 -D

  3. 安装检查规则库:npm i eslint-config-airbnb-base@15 eslint-plugin-import@2

    ellint-config-airbnb-base定制了一套标准的、常用的js语法检查规则,推荐使用。

    eslint-plugin-import用于将js语法检查规则进行导入,配合eslint-loader使用。

    // webpack.config.js
    module:{
        rules:[
            {
                test: /\.js$/,
                exclude: /node_modules/,
                // 优先执行,原因是eslint和babel都是对js文件进行处理,所以两者之间要指定执行的顺序
                enforce: 'pre',
                // 对js进行语法检查
                loader: 'eslint-loader', // eslint-loader依赖于eslint
                options: {
                    fix: true // 若有问题自动修复,重要!
                }
            }
        ]
    }
    
    // package.json
    "eslintConfig": {
        "extends": "airbnb-base", // 直接使用airbnb-base提供的语法规则
        "env": {
            "browser": true // 支持浏览器端全局变量
        }
    }
    
  4. 若出现:warning Unexpected console statement no-console警告,意思是不应该在项目中写console.log();若想忽略,就在要忽略检查代码的上方输入一行注释: eslint-disable-next-line即可。

2.10 js兼容性处理

  1. 概述:将浏览器不能识别的新语法转换成原来的旧语法,做浏览器兼容性处理
  2. 安装:npm i babel-loader@8 @babel/core@7 -D

兼容性处理方式

1. 基本js兼容性处理

  1. 安装:npm i @babel/preset-env@7 -D

    // webpack.config.js
    module: {
        rules: [
            test: /\.js$/,
            exclude: /node_modules/,
            use: [{
            	// 将es6语法转换为es5语法(即做浏览器的兼容性处理)
            	loader: "babel-loader",
            	options: {
            		// 预设:只是babel做怎么样的兼容性处理,@babel/preset-env处理最基本的兼容性问题
            		presets: ["@babel/preset-env"]
            	}
            }]
        ]
    }
    
  2. 问题:使用@babel/preset-env 只能处理简单的语法,promise等无法处理。

2. 全部js兼容性处理

  1. 安装:npm i @babel/polyfill -D ,此包要安装在生产依赖中,而非开发依赖。

    // index.js 入口文件
    import "@babel/polyfill"
    
  2. 问题:虽然可以完成高级es6语法的转换,但缺点是所有都转换,无法按需转换,生成的js体积大。

3. 按需处理js兼容性

  1. 安装:npm i core-js@3 -D

  2. 注意点:正常来讲,一个1文件只能被一个loader处理。当一个文件要被多个loader处理,那么一定要指定loader的执行顺序,故core-js兼容性处理eslint语法检查的执行顺序不能颠倒,先执行语法检查后执行兼容性处理。因为core-js的作用原理是,要在入口文件中导入core-js中相应的js文件;而eslint中排除了node_modules文件夹,当eslint检查到core-js导入的文件时,就会发生错误。

    // webpack.config.js
    module: {
        rules: [
    		test: /\.js$/,
            exclude: /node_modules/,
    		use: [{
            	loader: "babel-loader",
            	options: {
            		presets: [
            			[
            				"@babel/preset-env",
            				{
            					// 按需加载
            					useBuiltIns: "usage",
            					// 指定core-js版本
            					corejs: {
            						version: 3
            					},
                                // 指定兼容性做到哪个版本的浏览器
                                targets: {
                                    chrome: "60",
                                    firefox: "60",
                                    ie: "9",
                                    safari: "10",
                                    edge: "17"
                                }
            				}
        				]
        			]
            	}
            }]
        ]
    }
    

2.11 压缩html、js

  1. 直接修改webpack.prod.js中的mode为production即可。

  2. 若设置了模式为production,必须在new HtmlWebpackPlugin时添加配置minify: false。

    new HtmlWebpackPlugin({
        minify: {
            collapseWhitespace: true, // 去除空格
            removeComments: true // 去除注释
        }
    })
    

3 优化代码调试

3.1 source-map

  1. source-map:一种提供源代码到构建后代码映射技术,若构建后代码出错了,通过映射可以追踪源代码错误。它会生成一个 xxx.map文件,里面包含源代码和构建后代码每一行、每一列的映射关系。当构建后代码出错了,会通过 xxx.map文件,从构建后代码出错位置找到映射后源代码出错位置,从而让浏览器提示源代码文件出错位置,帮助我们更快的找到错误根源

    // webpack.config.js
    module: {
        ...
    },
    plugins: [],
    devtool: '[inline-|eval-|hidden-][nosources-][cheap-[module-]]source-map'
    
  2. 组合:[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map

    source-map:生成外部.map文件,提示错误代码准确信息和源代码的错误位置。

    inline-source-map:在js文件中生成内联source-map,格式为base64,提示错误代码准确信息和源代码的错误位置。

    eval-source-map:在每个js文件中生成对应source-map,都在eval,提示错误代码准确信息和源代码的错误位置。

    hidden-source-map:生成外部文件,提示错误代码原因,但没有错误位置,不能追踪源代码错误,只能提示到构建后代码的错误位置。

    nosource-source-map:生成外部文件,提示错误代码原因,但是没有任何源代码信息。(用于生产环境)

    cheap-source-map:生成外部文件,提示错误代码原因和源代码的错误位置,只能精确到行。(默认精确到列)

    cheap-module-source-map:生成外部文件,提示错误代码原因和源代码的错误位置。除外,还会将loader的进行加入。

  3. 内联和外部:外部生成了.map文件,内联没有;内联构建速度更快。

  4. 开发环境:速度快,调试更友好(一般选择内联)

    ​ 速度快(eval > inline > cheap > …)

    eval-cheap-source-map > eval-source-map

    ​ 调试更友好

    source-map > cheap-module-source-map > cheap-source-map

​ 综上:开发环境选择eval-source-map > eval-cheap-module-source-map

  1. 生产环境:源代码是否要隐藏?调试是否要友好?(内联会让代码体积变大,故在生产环境下不用内联)

    隐藏:nosource-source-map 全部隐藏hidden-source-map 只会隐藏源代码,会提示构建后代码错误信息

​ 调试:source-map > cheap-module-source-map

4 优化打包构建速度

4.1 HMR

  1. HMR:hot module replacement 热膜块替换,在devServer中添加hot: true即可。

  2. 作用:一个模块发生变化,只会重新打包这一模块(而不是打包所有的模块),极大提升了构建速度。

  3. 样式文件:可以使用HMR功能,因为style-loader内部实现了热模块替换功能,在开发环境下,必须使用style-loader而不能使用mini-css-extract-webpack-plugin

  4. html文件:默认不能使用HMR功能(webpack5中默认能使用),修改的同时html文件的内容也不会发生变化(解决这个问题需要在entry入口,将html文件引入)。值得注意的是,html只有一个文件,没有必要进行HMR

  5. js文件:默认不能使用HMR功能,若要实现则需要修改js代码,添加支持HMR功能的代码

    // index.js 入口文件
    // 兼容性处理
    if(module.hot){ // 一旦 module.hot 为true,说明开启了HMR功能,就可以让HMR功能代码生效
    	// module.hot.accep会监听 print.js 文件的变化,一旦发生变化,其他模块不会重新打包构建
        module.hot.accept('./print.js'); 
    }
    

    注意:HMR功能只能处理非入口js文件

​ 上面这样写会很麻烦,所以实际开发我们会使用其他 loader 来解决,比如vue-loaderreact-hot-loader

4.2 oneOf

  1. 默认情况下,每个文件都需要webpack定义的loader检验一遍,这就会使打包构建效率变得很慢。
// webpack.config.js
module: {
    roules: [
        {
            loader: 'eslint-loader'
        },
        {
            // oneOf:作用是当匹配到了符合自己的loader时就不再往下匹配了,
            // 要求每个loader匹配的都不能重复,若重复,则提取出来,否则只有最先匹配的loader生效
            oneOf: [
                {
                    loader: ''
                },
                {
                    loader:''
                }
            ]
        }
    ]
}

4.3 cache缓存

  1. 定义:对 Eslint 检查 和 Babel 编译结果进行缓存。
  2. 作用:每次打包时 js 文件都要经过 Eslint 检查 和 Babel 编译,速度比较慢。可以缓存之前的 Eslint 检查 和 Babel 编译结果,这样第二次打包时速度就会更快了。

eslint缓存

  1. 若是用plugin进行语法检查,写法如下:

    // webpack.confg.js
    plugins: [
        new ESLintWebpackPlugin({
          // 指定检查文件的根目录
          context: path.resolve(__dirname, "../src"),
          exclude: "node_modules", // 默认值
          cache: true, // 开启缓存
          // 缓存目录
          cacheLocation: path.resolve(__dirname, "../node_modules/.cache/.eslintcache"),
        }),
    ]
    

babel缓存

  1. babel缓存,目的是让第二次打包构建的速度更快,优化打包速度。

    // webpack.config.js
    {
        loader: 'babel-loader',
        options: {
            presets: [],
            // 开启babel缓存,第二次构建是,会读取之前的缓存
            cacheDirectory: true,  
            // 关闭缓存文件的压缩,压缩会消耗时间,缓存在内存中,对上线无影响
    		cacheCompression: false
        }
    }
    

4.4 多进程打包

  1. 多进程打包需要借助thread-loader来完成,进程启动大概需要600ms,进程通信也有开销,只有工作消耗时间比较长,才需要多进程打包,若滥用的话只会更慢

    // webpack.config.js
    module:{
        rules:[
    		{
                // 一般情况下,主要是打包js文件,因为一个项目中js居多
        		test:/\.js$/,
        		use:[
                    {
                        // 多进程loader需要放置在功能loader的后面
                        loader:'thread-loader',
                        options:{
                            workers: 2 // 开启进程为2个
                        }
                    },
                    {
                        loader: 'babel-loader',
                    }
                ]
            }
        ]    
    }
    

4.5 externals

  1. externals:打包的时候拒绝把第三方库打包进来,此时第三方库就需要在index.html中手动通过cdn的方式引进来

    // webpack.config.js
    mode: "production",
    externals:{
        // 拒绝jQuery被打包进来
        jquery: "jQuery" // jquery是库名:jQuery是npm下的包名
    }
    

5 优化代码运行的性能

5.1 dll(动态链接库)

  1. dll作用:默认情况下,第三方库在code split下是将项目所用的库都打包成一个js文件,那么这个js文件就非常大,利用dll技术可以将用到的第三方库打包成不同的js文件(即分别进行单独打包)以达到减少每个文件体积的目的

  2. externals和dll的区别:externals拒绝打包第三方库,使用时则通过cdn的方式引入(第三方库不需要部署在服务器);dll是提前将第三方库打包好,使用时直接引入打包好的即可(第三方库需要部署在服务器)。

  3. 配置,在webpack.config.js的同级目录下新建webpack.dll.js。单独打包第三方库,需运行webpack --config webpack.dll.js

    // webpack.dll.js
    const webpack = require("webpack") // webpack自带的插件,无需下载
    module.exports = {
        entry:{
            jquery: ["jquery"], // 第一个jquery是最终打包生成的[name],数组里面的jquery是要打包的库
            vue: ["vue"]
        },
        output:{
            filename: "[name].js",
            path: resolve(__dirname, "dll"),
            library: "[name]_[hash]" // 打包的库里面向外暴露出去的内容叫什么名字
        },
        plugins:[
            // 打包生成一个 manifest.json,提供和jquery映射,目的是webpack打包的时候标记此库无需打包
            new webpack.DllPlugin({
                name: "[name]_[hash]", // 映射库向外暴露的内容的名称
                 // 每个第三方库对应一个映射文件
                path: resolve(__dirname, "dll/[name].manifest.json") // 输出文件路径
            })
        ],
        mode: "production"
    }
    
  4. webpack.config.js配置

    // webpack.config.js
    const webpack = require('webpack')
    plugins:[
        // 告诉webpack哪些库不需要打包,同时使用时的名称也得变
        new webpack.DllReferencePlugin({
            manifest: resolve(__dirname, "dll/jquery.manifest.json"), // 进行webpack.dll.js打包时生成的映射文件
            manifest: resolve(__dirname, "dll/vue.manifest.json")
        }),
        // 每次打包时在html文件中引入dll文件夹里面的第三方资源
        new AddAssetHtmlWebpackPlugin({
            filepath: resolve(__dirname, "dll/jquery.js"),      
            filepath: resolve(__dirname, 'dll/vue.js'),
        })
    ]
    

5.2 network cache缓存

  1. 文件资源缓存,目的是让代码运行缓存更好使用

​ 浏览器向服务器请求数据时,除了第一次向服务器索取数据,后面就强制走浏览器的缓存了(数据缓存未过期)。若此时服务器重新部署了新的资源,浏览器会请求不到新的文件资源。为了解决这个问题,可以对服务器的文件资源进行hsah值命名,作为文件资源的版本号。

  1. 配置

    // webpack.config.js  --> mode: "production"
    module.exports = {
        entry: "./src/js/index.[contenthash:10].js",
        plugins: [
            new MiniCssExtractWebpackPlugin({
                filename: "css/built.[contenthash:10].css"
            })
        ]
    }
    
    1. hash:每次webpack构建时都会生成一个唯一的hash值。只要是属于同一次构建打包的,每个文件的hash值都一样,这时就会产生一个问题,如果重新打包就会导致所有缓存失效,无论改动多少个文件。
    2. chunkhash:根据chunk生成的hash值。若打包来源于同一个chunk(即来自同一个入口),那么hash值就一样,因为css是在js中被引入的,所以同属于一个chunk,也并不能做到js和css文件产生独立hash值。
    3. contenthash:根据文件内容生成hash值。不同文件hash值一定是不一样的。(一般使用此等hash值)

5.3 tree shaking

  1. 概述:有时候,我们一个模块向外暴露了n个函数、对象或者其他一些数据,但是我们只是用到了其中的几个,那在最终打包的时候,我们只希望把我们所用的打包进去,这时候就要用到tree-shaking,即去除无用代码,以达到减少代码体积的目的

  2. 配置:同时满足两个条件webpack会自动开启tree-shaking

    1)使用ES6模块化;2)开启production环境

  3. 由于webpack版本的原因,打包的时候可能会出现去除入口文件引入的css、less、@babel/polyfill,为了防止出现此等原因,需要在package.json中配置"sideEffects":["*.css", "*.less"],这样子就不会把入口文件的css、less文件意外去除掉。

5.4 code split

code split代码分割,将一个js文件分割成多个js文件。默认情况下,在单入口文件下,项目中用到的js代码都会被打包成为一个bundle,这就会使得该bundle体积大。

  1. 方法一:从入口文件入手,采用多入口文件时,就不需要在index.js(原单文件入口时的入口文件)引入了。

    // webpack.config.js
    entry: './src/js/index.js' --> 单入口,对应单页面应用
    entry: {
        // 多入口:有多少个入口文件就输出多少个bundle
        index: "./src/js/index.js",
        test: "./src/js/test.js"
    },
    output: {
        path: resolve(__dirname, "build"),
        filename: "js/[name].[contenthash:10].js", --> 这样就可以避免每个js文件名字重复
    }
    

​ 缺点:有多少个js文件就需要在配置文件里面添加,重复修改,不够灵活。

  1. 方法二:配置optimization。(可以与多入口文件用)

    1. 若多个js文件中都引入了一个模块,打包时会将该模块都打包到各自引用的js文件(即每个js文件都会有该模块一样的代码),为了防止这种情况,可以将该模块单独打包到一个js文件,其他js文件要使用就引入即可,极大减少代码体积,提高代码复用率,此时就需要配置optimization。

    2. 若是多入口文件,则自动分析多入口chunk中有没有公共的文件,若有会打包成单独一个bundle。

    3. 若是单入口文件,则将项目用到的第三库中的代码单独打包成一个js文件最终输出。

    // webpack.config.js
    optimization: {
        splitChunks: {
            chunks: "all"
        }
    },
    mode: "production"
    
  2. 方法三:import动态导入语法,能将某个文件单独打包。在单入口文件index.js中,通过js代码,通过按需导入方式,让某个js文件被单独打包成一个文件。再配合optimization。

    解决动态导入import语法报错问题 --> 实际使用eslint-plugin-import的规则解决的,故需要下载eslint-plugin-import插件。

    // index.js 单入口文件
    /*webpackChunkName:'test'*/:为这个文件打包输出的时候命名,还需要配置output:{chunkFilename: [name].chunk.js}
    import(/* webpackChunkName: 'test' */'.test')
    .then((mul, count)=>{
       // 文件加载成功
    })
    .catch(()=>{
       // 文件加载失败
    })
    
  3. 方案:单文件入口 + optimization + 入口文件import

5.5 懒加载和预加载

  1. 懒加载:当文件使用时才加载。放在监听事件的回调函数中(异步事件)。

  2. 预加载(prefecth):会在使用之前提前加载js文件。(兼容性很差,慎用)

    // index.js 入口文件
    document.eventListen = function(){
        // 该写法与code split中的按需导入一样
        import(/*webpackChunName: 'test', webpackPrefetch: true*/'./test').then(()=>{
            // 文件加载成功
        })
    }
    
  3. 预加载和正常加载区别:正常加载可以认为是并行加载(同一时间加载多个文件);预加载是其他资源正常加载后,浏览器空闲了,再偷偷加载资源。

5.6 PWA

  1. PWA:渐进式网络开发应用程序(离线可访问技术)

  2. PWA需要借助workbox插件来完成,workbox-webpack-plugin

    // webpack.config.js
    plugins:[
        new WorkboxWebpackPlugin.GenerateSW({
            // 作用:帮助serviceWorker快速启动,删除旧的serviceWorker。
            // 打包后生成 serviceworker 配置文件
            clientsClaim: true,
            skipWaiting: true
        })
    ]
    
  3. 配置注册serviceWorker,注册完成后需要运行在服务器

    // index.js 入口文件
    // 处理兼容性问题
    if("serviceWorker" in navigator){
        // 绑定监听事件,等待全局load加载完毕
        window.addEventListener("load", ()=>{
            // 全局load加载完毕后,开始注册serviceWorker。注册时传入的参数文件是workbox-webpack-plugin打包的chunk
            navigator.serviceWorker.register("/service-worker.js"). 
            then(()=>{ 
                // 注册成功 
            })
            .catch(()=>{
                // 注册失败
            })
        })
    }
    

6 Webpack5

代码优化

从 4 个角度对 webpack 和代码进行了优化:

  1. 提升开发体验

    1. 使用 Source Map 让开发或上线时代码报错能有更加准确的错误提示。
  2. 提升 webpack 提升打包构建速度

    1. 使用 HotModuleReplacement 让开发时只重新编译打包更新变化了的代码,不变的代码使用缓存,从而使更新速度更快。

    2. 使用 OneOf 让资源文件一旦被某个 loader 处理了,就不会继续遍历了,打包速度更快。

    3. 使用 Include/Exclude 排除或只检测某些文件,处理的文件更少,速度更快。

    4. 使用 Cache 对 eslint 和 babel 处理的结果进行缓存,让第二次打包速度更快。

    5. 使用 Thead 多进程处理 eslint 和 babel 任务,速度更快。(需要注意的是,进程启动通信都有开销的,要在比较多代码处理时使用才有效果)

  3. 减少代码体积

    1. 使用 Tree Shaking 剔除了没有使用的多余代码,让代码体积更小。

    2. 使用 @babel/plugin-transform-runtime 插件对 babel 进行处理,让辅助代码从中引入,而不是每个文件都生成辅助代码,从而体积更小。

    3. 使用 Image Minimizer 对项目中图片进行压缩,体积更小,请求速度更快。(需要注意的是,如果项目中图片都是在线链接,那么就不需要了。本地项目静态图片才需要进行压缩。)

  4. 优化代码运行性能

    1. 使用 Code Split 对代码进行分割成多个 js 文件,从而使单个文件体积更小,并行加载 js 速度更快。并通过 import 动态导入语法进行按需加载,从而达到需要使用时才加载该资源,不用时不加载资源。

    2. 使用 Preload / Prefetch 对代码进行提前加载,等未来需要使用时就能直接使用,从而用户体验更好。

    3. 使用 Network Cache 能对输出资源文件进行更好的命名,将来好做缓存,从而用户体验更好。

    4. 使用 Core-js 对 js 进行兼容性处理,让我们代码能运行在低版本浏览器。

    5. 使用 PWA 能让代码离线也能访问,从而提升用户体验。

7 基础配置

7.1 处理图片资源

Webpack4 :处理图片资源通过 file-loaderurl-loader 进行处理。

Webpack5: 已经将两个 Loader 功能内置到 Webpack 里了,只需要简单配置即可处理图片资源。

// webpack.config.js
module:{
    rules:[
        {
            test: /\.(png|jpe?g|gif|webp)$/,
            type: "asset",
            parser: {
                dataUrlCondition: {
                    maxSize: 10 * 1024 // 小于10kb的图片会被base64处理
                }
            },
            generator: {
          		// [query]: 添加之前的query参数
          		filename: "static/imgs/[hash:8][ext][query]",
        	},
        }
    ]
}

7.2 自动清空上次的资源

// webpack.config.js
output: {
    clean: true // 自动将上次打包目录清空
}

7.3 处理其他资源

// webpack.config.js
module: {
    rules: [
        {
            test: /\.(ttf|woff2?|mp4|mp3|avi)$/,
            type: "asset/resource",
            generator: {
                filename: "static/media/[hash:10][ext][query]"
            }
        }
    ]
}

7.4 js语法检查

Eslint:可组装的 JavaScript 和 JSX 检查工具。这句话意思就是:它是用来检测 js 和 jsx 语法的工具,可以配置各项功能。使用 Eslint,关键是写 Eslint 配置文件,里面写上各种 rules 规则,将来运行 Eslint 时就会以写的规则对代码进行检查。

1. 配置文件

  1. 配置文件有很多种写法:

    ​ 新建文件,位于项目根目录,.eslintrc.eslintrc.js.eslintrc.json,以上文件区别在于配置格式不同,通常使用.eslintrc.js

    package.jsoneslintConfig:不需要创建文件,在原有文件基础上写Eslint会自动查找和读取它们。

// .eslintrc.js
module.exports = {
    // 解析选项
    parserOptions: {
        ecmaVersion: 6, // ES语法版本
        sourceType: "module", // ES模块化
        ecmaFeatures: { // ES其他特性
            jsx: true // 如果是React项目,就需要开启jsx语法
        }
    },
    // 具体检查规则
    rules: {
        // "off"或0 -> 关闭规则;
        // "warn"或1 -> 开启规则,使用警告级别的错误,warn不会导致程序退出;
        // "error"或2 -> 开启规则,使用错误级别的错误,error被触发程序会退出。
    },
    // 继承其他规则
    extends: [
        // 开发中一点点写rules规则太费劲,所以有更好的办法,继承现有的规则
        // 现有以下有名的规则
        // Eslint官方规则:eslint:recommended
        // Vue Cli官方规则:plugin:vue/essential
        // React Cli官方规则:react-app
    ]
}

2. Webpack中使用

  1. 下载npm i eslint-webpack-plugin eslint -D
// webpack.config.js
const EslintWebpackPlugin = require("eslint-webpack-plugin");
module.exports = {
    plugins:[
        new EslintWebpackPlugin({
            // 指定检查文件的根目录
            context: path.resolve(__dirname, "src");
        })
    ]
}
// .eslintrc.js
module.exports = {
    // 继承Eslint规则
    extends: ["eslint:recommended"],
    env: {
        node: ture, // 启用node中全局变量
        browser: true, // 启用浏览器中全局变量
    },
    parserOptions: {
        ecmaVersion: 6,
        sourceType: "module"
    },
    rules: {
        "no-var": 2, // 不能使用var定义变量
    },
    plugins: ["import"], // 解决动态导入语法报错
}

3. VSCode Eslint 插件

​ 在VSCode中下载Eslint插件即可,不用编译就能看到错误,可以提前解决问题。但此时所有文件都会被Eslint插件所检查,dist目录下的打包文件就会报错,但只需检查src下面的文件,无需检查dist目录下的文件。此时要在项目根目录新建.eslintignore文件

// .eslintignore
# 忽略dist目录下的所有文件
dist

7.5 js兼容性处理

Babel:主要用于将ES6语法编写的代码转换为向后兼容的js语法,以便能够运行在当前和旧版本的浏览器或其他环境中。

1. 配置文件

  1. 配置文件有很多种写法:

    ​ 新建文件,位于项目根目录,babel.config.jsbabel.config.json.babelrc.babelrc.js.babelrc.json

    package.jsonbabel:不需要创建文件,在原有的基础上配置。

    Babel会查找和自动读取他们,所以以上配置文件只需存在一个即可。

    // babel.config.js
    module.exports = {
        // 预设
        presets: [],
    }
    

    presets预设:简单理解就是一组Babel插件,扩展Babel功能。

    1. @babel/preset-env:一个智能预设,允许使用最新的JavaScript。
    2. @babel/preset-react:一个用来编译React jsx语法的预设。
    3. @babel/preset-typescript:一个用来编译TypeScript语法的预设。

2. 在Webpack中使用

  1. 下载npm i babel-loader @babel/core @babel/preset-env -D

    // babel.config.js
    module.exports = {
        presets: [
            "@babel/preset-env"
        ]
    }
    
    // webpack.config.js
    module.exports = {
        module: {
            rules: [
                {
                    test:/.\js$/,
                    exclude: /node_modules/,
                    loader: "babel-loader"   
                }
            ]
        }
    }
    

7.6 css压缩

  1. 下载npm i css-minimizer-webpack-plugin -D

    // webpack.config.js
    const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");
    plugins:[
        new CssMinimizerWebpackPlugin(); // 压缩css
    ]
    // 也可以将css压缩写到optimization.minimizer里面
    optimization:{
        minimizer:[new CssMinimizerWebpackPlugin()]
    }
    

8 优化打包构建速度

8.1 多进程打包

  1. 定义:多进程打包就是开启电脑的多个进程同时干一件事,速度更快。

  2. 作用:当项目越来越庞大时,打包速度越来越慢,甚至于需要一个下午才能打包出来代码。这个速度是比较慢的。我们想要继续提升打包速度,其实就是要提升 js 的打包速度,因为其他文件都比较少。而对 js 文件处理主要就是 eslint 、babel(babel多进程打包跟webpack4一样)、Terser(js压缩工具) 三个工具,所以我们要提升它们的运行速度。我们可以开启多进程同时处理 js 文件,这样速度就比之前的单进程打包更快了。

  3. 下载包npm i -D thread-loader terser-webpack-plugin

    // webpack.config.js
    // nodejs核心模块,直接使用
    const os = require("os");
    // cpu核数
    const threads = os.cpus().length;
    // 压缩js的插件,在该插件内进行压缩js代码的多进程配置
    const TerserWebpackPlugin = require("terser-webpack-plugin");
    plugins: [
        new ESLintWebpackPlugin({
            threads, // 为语法检查开启多进程
        })
    ],
    optimization: {
        minimizer: [
            // 当mode: "production"时,会默认开启js代码压缩,但是需要进行其他配置时,就需要重新写了
            new TerserWebpackPlugin({
                parallel: threads // 开启js代码压缩多进程
            })
        ]
    }
    

9 优化代码运行性能

9.1 Babel

  1. @babel/plugin-transform-runtime: 禁用了 Babel 自动对每个文件的 runtime 注入,而是引入 @babel/plugin-transform-runtime 并且使所有辅助代码从这里引用。

  2. Babel 为编译的每个文件都插入了辅助代码,使代码体积过大!Babel 对一些公共方法使用了非常小的辅助代码,比如 _extend。默认情况下会被添加到每一个需要它的文件中,将这些辅助代码作为一个独立模块,来避免重复引入,从而减少代码体积。

  3. 下载npm i @babel/plugin-transform-runtime -D

    // webpack.config.js
    {
        loader: "babel-loader",
        options: {
            plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
        }
    }
    

9.2 压缩图片

  1. 开发如果项目中引用了较多图片,那么图片体积会比较大,将来请求速度比较慢。我们可以对图片进行压缩,减少图片体积。

    注意:如果项目中图片都是在线链接,那么就不需要了。本地项目静态图片才需要进行压缩。

  2. 下载包:npm i -D image-minimizer-webpack-plugin imagemin

    无损压缩:npm i -D imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo

    有损压缩:npm i -D imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo

    // webpack.config.js
    const ImageMinimizerWebpackPlugin = require("image-minimizer-webpack-plugin");
    optimization: {
        minimizer: {
          // 压缩图片
          new ImageMinimizerPlugin({
            minimizer: {
              implementation: ImageMinimizerPlugin.imageminGenerate,
              options: {
                plugins: [
                  ["gifsicle", { interlaced: true }],
                  ["jpegtran", { progressive: true }],
                  ["optipng", { optimizationLevel: 5 }],
                  [
                    "svgo",
                    {
                      plugins: [
                        "preset-default",
                        "prefixIds",
                        {
                          name: "sortAttrs",
                          params: {
                            xmlnsOrder: "alphabetical",
                          },
                        },
                      ],
                    },
                  ],
                ],
              },
            },
          }),
        }
    }
    
  3. 此时运行会出错,需要安装两个文件到 node_modules 中才能解决

    jpegtran.exe:需要复制到 node_modules\jpegtran-bin\vendor 下面,官网http://jpegclub.org/jpegtran/

    optipng.exe:需要复制到 node_modules\optipng-bin\vendor 下面,官网https://optipng.sourceforge.net/

你可能感兴趣的:(webpack,javascript,学习)