• Webpack5


      Webpack是一款模块打包工具,可以把多个文件打包成一个或几个文件,它不仅能打包JS文件, 还能打包css, image等静态资源。当然,在默认情况下,它只打包JS文件和JSON文件,因为它只认识JS文件和JSON文件。  

    mkdir webpack-demo
    cd webpack-demo
    npm init -y

      创建webpack-demo项目,并创建package.json 文件,管理项目依赖和脚本命令。npm install webpack webpack-cli --save-dev,安装webpack。webpack4把webpack命令抽离出来,形成了一个单独的包webpack-cli ,安装它,才能在命令行中使用webpack命令。为什么要单独形成一个包呢?因为webpack-cli 还提供了两个其它的功能,init 和 migrate命令,使我们快速创建webpack配置和提供升级迁移。不过这两个基本不用,了解一下就可以了。只要记住安装webpack的同时安装上webpack-cli就可以了。mkdir src 存放源文件,touch src/index.js, touch src/component.js 文件,component.js 文件

    export default (text = 'hello world') => {
      const element = document.createElement('div');
      element.innerHTML = text;
    
      return element;
    }

      index.js 文件

    import component from './component';
    document.body.appendChild(component());

      npx webpack 生成了dist 目录,打包成功了。npm5之后,npx可以执行命令。webpack是怎么打包的?不是要写配置文件吗?webpack4提供零配置,没有配置文件,也能打包。webpack进行打包时,如果没有配置文件,默认入口是src/index.js,默认输出目录是dist,文件名是main.js. 但有一个WARNING,

      没有设置mode,webpack4 提供了一个mode 配置项 ,用什么模式进行打包,模式不同,打包后的文件内容不同。它有三个选项development, production, none. 看名字也就知道了,development 开发模式,这种模式下有利于开发,production,生产模式,这有利于生产环境。none就是什么都不做,很少用到它。mode 可以在命令行进行配置,   npx webpack --mode development ,查看一下dist/main.js,没有压缩。npx webpack --mode produciton,生成的代码压缩了。开发模式下,没有压缩,而生产模式下,压缩代码。和我们想的是一样的,线上的代码都是压缩代码,开发肯定不压缩,要不然没有调试。 随着指令越来越长,手动输入就比较麻烦了,可以使用npm run 命令,在package.json的scripts的字段中

      "scripts": {
        "dev": "webpack --mode development",
        "build": "webpack --mode production"
      }

      npm run build或npm run dev都能打包成功。怎么验证一下打包后的文件是正确的?手动建一个html 文件,script 引入 dist/main.js,这肯定没有问题。但有了webpack后,能自动化就自动化。webpack提供一个插件html-webpack-plugin,它会创建html文件,并自动引入打包后文件。npm i html-webpack-plugin -D,不过,这要写webpack配置文件了,零配置此时无能为力了。webpack有production和development两种mode,这也提醒我们要针对不同的环境,提供不同的配置,一种用于开发,一种用于生产。基本有两种实现方式,一个配置文件和多个配置文件。一个配置文件时,执行webpack命令时,提供环境变量,配置文件中获取到环境变量,根据不同的值,提供不同的配置。webpack  --env production,production就是环境变量, 如果没有设置值,那就true. 此时,配置文件中要exports 出一个函数,函数的第一个参数就环境变量,函数返回配置对象。新建webpack.config.js,webpack默认的配置文件名,webpack打包的时候,自动会找webpack.config.js文件,

    module.exports = (env) => {
        if (env === 'production') {
            return ({
                // 生产环境配置英
            })
        } else {
            return ({
                // 开发环境配置英
            })
        }
    }

      多个配置文件就是为每一个环境提供一个配置文件,执行webapck命令时,用--config指定使用哪一个配置文件。我更倾向于使用多个配置文件,简洁明了。新建webpack.dev.config.js.  配置文件有几个重要的概念,entry, output, module, plugins。

      entry:打包的入口文件,就是webpack在进行打包的时候,从哪个文件开始。

      output:打包后的文件放到什么地方,以及文件名是什么

      module:处理哪些模块,用什么规则处理,规则就是指定loaders。loaders就是告诉webpack怎么处理不认识的文件,把它转化成JS,因为webpack只认识js文件, 只有转化成JS,webpack才能进行打包

      plugins:打包过程其它的事情,比如压缩,生成html文件,扩展了webpack的功能

      由于有零配置,webpack提供了默认的entry和output, 如果觉得ok,配置文件中可以只写module 和plugins,mode也可以在配置文件中配置,npm run 命令中的 --mode去掉,webpack.dev.config.js如下

    const  HtmlWebpackPlugin  = require("html-webpack-plugin");
    module.exports = {
        mode: 'development',
        plugins: [
            new HtmlWebpackPlugin(),
        ],
    };

      如果觉得不ok 的话,可以写entry 和output, 把它覆盖掉。webpack.dev.config.js如下

    const path = require('path');
    const htmlWebpackPlugin = require('html-webpack-plugin'); // 引入插件
    
    module.exports = {
        mode: 'development',
        entry: path.join(__dirname, 'src/index.js'),
        output: {
            path: path.join(__dirname, 'dist'),
            filename: 'main.js'
        },
        plugins: [
            new htmlWebpackPlugin()
        ]
    }

      output的path要使用绝对路径,因此使用了Node.js内置path模块。plugins是个数组,每一个用到的插件都是数组中的一项。具体到每一个插件呢?插件都会暴露出构造函数,通过new 调用,就可以使用,如果插件还有配置项,就给构造函数传递一个配置对象作为参数。dev命令改成

     "dev": "webpack --config webpack.dev.config.js",

      npm run dev 打包成功,查看一下,没有问题。

      但前端不止JS,还有CSS,图片等。想要把页面更美观一点,写一点CSS,在src下新建style.css

    body {
        background: cornsilk;
    }

      并index.js引入,打包,发现wepack 不认识CSS,要配置loader。处理CSS两个基本loader,css-loader和style-loader, npm i css-loader style-loader -D

    module.exports = {
        mode: 'development',
        module: {
            rules: [
                { test: /\.css$/, use: ["style-loader", "css-loader"] },
            ],
        },
        plugins: [
            new HtmlWebpackPlugin(),
        ],
    };

      当使用多个loader来处理同一个文件时,要注意loader的执行顺序,它是按照从右到左,从下到上的顺序执行。css-loader 先执行,然后把执行结果交给style-loader,最终style-loader返回js,这样webpack才能打包。css-loader:将css的代码按照commonJS的规范转化成js,将css代码输出到打包后的JS文件中。style-loader是把包含css内容的JS代码,挂载到页面的<style> 标签中。style 是window对象属性。

      当在一个js文件中引入css文件时,css-loader会把css代码放到最后的打包文件中。这时如果新建一个html页面,用src引入bundle.js,css文件并不生效,页面并没有样式。需要使用style-loader,重新进行打包,这时html引入新的打包后的文件,样式显示,查看页面源代码,可以发现header中有了style 标签,原来这就是style-loader的作用。它将样式挂载到window对象的style 属性。

      处理图片,webpack内置Asset Modules(资源模块),代替了url-loader, file-laoder

    {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
    }

      Asset Modules有几种type: asset/resource: 创建一个单独的文件并导出它的url,此时如果你import image或在css中url(image), 这个image会作为一个单独的文件被打包到输出目录。 asset/inline: 导出一个资源的data url,如果资源是图片,它会打包成base64编码,内联到打包生成的文件中。asset/source: 导出资源的源代码,把文件当作一个字符串,比如import txt文本。asset: 在导出一个data url 和创建一个单独文件之间进行选择,到底选择哪一种形为,依赖资源的大小。通过parse进行设置

    {
        test: /\.(png|jpg)$/,
        type: "asset",
        parser: { dataUrlCondition: { maxSize: 8000 } },
    }

      Dataurl 就是base64编码,也就是说,当资源大于8kb时,输出为单独的文件,小于8kb,输出base64编码,直接内嵌入打包后文件中。在style.cs中引入背景图片

    body {
        background: cornsilk;
        background-image: url("./logo.png");
        background-repeat: no-repeat;
        background-position: center;
    }

      也可以在js中直接引入图片

    import src from "./logo.png";
    
    export default (text = "Hello world") => {
        const element = document.createElement("div");
        element.innerHTML = text;
        const img = document.createElement('img');
        img.src = src;
        element.append(img)
        return element;
    };

      找一张图片,命名为logo.png, 放到src中,npm run dev 打包,没有问题。

      字体的处理和图片一样,也是使用内置的资源模块

    {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
    }

      配置好后,修改代码还是比较麻烦了,修改一下,就要npm run dev一下,怎么自动打包,并刷新浏览器页面呢?webpack-dev-server,npm i webpack-dev-server -D,script 命令改一下。

    "dev": "webpack server --config webpack.dev.config.js"

      npm run dev, 浏览器localhost:8080, 打开页面,改变样式,页面实时刷新。webpack-dev-sever是webpack自带的一个小型服务器,它会监测每个文件的变动,每当有文件改动时,就会重新打包,然后浏览器自动刷新页面,能时时看到代码的变动,这是所谓的liveload 或hotload(热更新)。webpack-dev-server 打包后文件是放到内存中的,而不像npm run build 把文件打包到硬盘上。想要访问服务器的文件,路径是http://[devServer.host]:[devServer.port]/[output.publicPath]/[output.filename],host默认是localhost, port是8080, publicPath默认是/, filename就是打包后文件。所以http://localhost:8080/就可以访问服务器的内容。如果想要配置webpack-dev-sever,配置文件提供了devServer 配置项,比如把端口改为9000

    devServer: {
        port: 9000,  // 设置端口号
        stats: 'errors-only', // 只有产生错误的时候,才显示错误信息,日志的输出等级是error.
        overlay: true // 当有编译错误的时候,在浏览器页面上显示。
    }

      npm run dev,重启服务器,这时看到项目启动在9000端口下。

      webpack-dev-server  还有两个配置项需要注意一下:

      static: webpack-dev-server 会把所有的静态文件(css, js, img 等)进行打包,放到服务器根目录下,供我们访问。但如果访问的资源不是由webpack-dev-server 打包生成的,它就找不到了,此时就要指定这些静态资源,比如index.html 文件,所在的位置,也就是static,否则就会显示404. 如果不使用webpack-html-plugin, webpack 是不会打包生成index.html的, 需要手动创建index.html, 这时index.html 就不是webpack-dev-server 打包生成的资源,就要指定它的位置。在浏览器中输入localhost:8080, 实际是向webpack-dev-server 请求index.html文件,如果webpack-dev-server 并没有生成这个文件,它就会static指定的目录去找。如果static没有配置,它会到项目根目录去找。由于都使用webpack-html-plugin,static也基本用不到。

      proxy: 代理,解决前端跨域问题,如axios.post(‘/api/login’), /api 就是标识,然后我们再在proxy 配置项里面给这个标识配置一个代理到的真实路径,如 ‘/api’ : ‘http://102.03.34.58/api’, 那么当我们调用接口的时候,实际上变成了http://102.03.34.58/api/login, 代理配置中的真实路径,就是会替换到请求中的标识

    module.exports = {
        devServer: {
            proxy: {
                '/api': 'http://102.03.34.58/api'
            },
        },
    }    

      但有时候,接口没有那么规范,可能有的以api 开始,有的没有api, '/api' 标识还可以是一个对象

    proxy: {
        '/api': {
           target: 'http://102.03.34.58',
           pathRewrite: { '^/api': '' }
        }
    }

      target 是请求的服务器地址,后面没有api, 使用这种方式配置以后,代理会在前端请求中的/api前面加上target, 相当于还是请求了 http://102.03.34.58/api/login,所以这里增加了pathRewrite 路径重写,所有将/api/xxx 变成 /xxx, 去掉/api,所以最后真实的请求路径中 http://102.03.34.58/login. pathRewrite 中的属性是正则表达式,^以什么开始, 值 呢?就是匹配到的路径重写成会什么。

      proxy 中的属性'/api', 是前端发送请求时,请求接口中的url要加的参数,当真正发送请求时,webpack 服务中配置的代理碰到api 就会拦截,然后把它变成我们配置的真实的路径

      在开发过程中,如果有代码报错,怎么找错呢?使用source maps, 因为webpack在打包之后,包裹你的代码到模块中,并且会注入额外的代码,以让程序运行,最麻烦的地方是,打包之后,只生成一个文件。这时,如果程序运行后有问题,浏览器开发工具,只会把问题定位到打包后的文件中,而不是打包之前的源代码,你也不知道具体哪个文件出错了。使用source maps后就不一样了,问题会直接定位到源代码出错的文件中,因为source maps文件就是源代码和打包后的代码之间的映射文件。配置soure maps,也很简单,devtools: ‘eval-source-map’。source  maps有很多类型,挑选适合自己的。

    devtool: "eval-source-map"

      假设在index.js中,console.log 写成了conso, 报错如下

     

       点击第二行的eval()就可以定位到源代码。

      开发环境的配置基本差不多了,那就再配置一下生产环境。新建webpack.prod.config.js,最基本的配置mode: production,和 html-webapck-plugin.

    const HtmlWebpackPlugin = require("html-webpack-plugin");
    module.exports = {
        mode: 'production',
        plugins: [
            new HtmlWebpackPlugin(),
        ]
    };

      script 的build命令改成

     "build": "webpack --config webpack.prod.config.js"

      抽离css到单独的文件。因为如果CSS在JS会造成页面的闪动。使用mini-css-extract-plugin 。它能把多个css文件打包成一个,因此,它有一个loader来处理抽取的工作。插件拿到loader生成的内容,输出一个单独的css文件。npm i mini-css-extract-plugin -D 

    const HtmlWebpackPlugin = require("html-webpack-plugin");
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    module.exports = {
        mode: 'production',
        module: {
            rules: [
                {
                    test: /\.css$/,
                    use: [MiniCssExtractPlugin.loader, 'css-loader']
                }
            ]
        },
        plugins: [
            new HtmlWebpackPlugin(),
            new MiniCssExtractPlugin({
                filename: 'style.css'
            })
        ]
    };

      css-loader 把 css文件按照commonJS 规范输出为JS,MiniCssExtractPlugin.loader 再把它抽取出来,最后MiniCssExtractPlugin把它输出为一个单独的CSS文件,命名为style.css。由于CSS中有背景图,还要处理一下

    module: {
        rules: [
            {
                test: /\.css$/,
                use: [MiniCssExtractPlugin.loader, 'css-loader']
            },
            {
                test: /\.(png|jpg)$/,
                type: "asset",
                parser: { dataUrlCondition: { maxSize: 8000 } },
            }
        ]
    },

      npm run build, 可以看到css输出到一个单独的文件中了。但CSS 并没有压缩,使用css-minimizer-webpack-plugin 进行压缩,npm i css-minimizer-webpack-plugin -D 

    const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
    
    module.exports = {
        mode: 'production',
        module: {...},
        plugins: [...],
        optimization: {
            minimizer: [
                `...`,
                new CssMinimizerPlugin(),
            ],
        }
    };

      module 和 plugins没有变化,这里就用...代替了。配置css压缩时,注意`...` 放到前面,因为在webpack5中,生产模式下,它自动会开启一些优化功能,比如JS的压缩。如要直接写css的压缩插件,就会把原有的优化功能覆盖掉。`...`  表示继承存在的压缩功能(extend existing minimizers)。

      处理JS代码。在开发环境下,并没有处理JS文件,因为自己开发用的浏览器一般都是最新的,绝大数JS功能都实现了,但生产环境可能要处理一下。因为用户使用什么浏览器,控制不了,有可能是旧版本的浏览器。处理JS文件用的是babel,npm i babel-loader, @babel/core, @babel/preset-env 等。

    {
        test: /\.js$/,
        exclude: /node_modules)/,
        use: "babel-loader"
    }

      创建babel的配置文件 babel.config.json 

    presets: [
            [
                "@babel/preset-env",
                {
                    "modules": false
                }
            ]
        ]

      babel的配置有点复杂,我有一篇专门的文章来介绍Babel 7

      如果想要线上环境debug,可以配置生产模式下source map

     devtool: "source-map"

      代码分割

      动态导入:使用import()语法,

     import(/* webpackChunkName: "optional-name" */ "./module").then(
        module => {...}
      ).catch(
        error => {...}
      );

      在src下面新建lazy.js

    export default "Hello from lazy";

      然后index.js 

    export default (text = "Hello world") => {
        const element = document.createElement("div");
        element.innerHTML = text;
    
        element.onclick = () =>
        import("./lazy")
          .then((lazy) => {
            element.textContent = lazy.default;
          })
          .catch((err) => console.error(err));
        return element;
      };

      注意使用lazy.default。当lazy.js用commonJS书写时,也要使用default。npm run build, 打包文件中多了958.js

       如要想要改变动态导入的模块输出的文件名,可以

    import(/* webpackChunkName: "lazy" */ "./lazy").then()

      也可以配置output.chunkFilename, 这个以后再说。

      打包文件分离

      多入口,提取公共模块,多入口就是配置文件中的entry可以是一个对象 {index: ‘./src/index’,  main: ‘./src/mian’}, 有几个入口文件,就输出几个bundle 文件。output filename: [name].js, 抽取css时,css的name也要改成[name].css, 每一个入口中所包含的css都会打包成一个单获的css文件。新建一个print.js

    import './style.css'
    export default function print() {
        console.log('print');
    }

       配置文件如下

    const path = require('path');
    module.exports = {
        mode: 'production',
        entry: {
            main: './src/index.js',
            print: './src/print.js'
        },
        output: {
            filename: "[name].js", // 双引号
            path: path.join(__dirname, "dist")
        },
        module: {...},
        plugins: [
            new HtmlWebpackPlugin(),
            new MiniCssExtractPlugin({
                filename: "[name].css" // 双引号
            })
        ],
        ......
    };

      npm run build,  dist 目录有点混乱了,output现在有一个clean配置,打包之前,它会先清理dist目录。

        output: {
            filename: "[name].js", // 双引号
            path: path.join(__dirname, "dist"),
            clean: true
        },

      但这种简单粗暴的多入口打包有个问题, 如果每一个入口中都引入机同的包,比如lodash,  那每一个入口对应的bundle都包含lodash,体积太大。npm i lodash, 并在index.js 和print.js 中引入,npm run build

       两个解决办法,一是dependOn, 

        entry: {
            main: {
                import: './src/index.js',
                dependOn: 'shared',
            },
            print: {
                import: './src/print.js',
                dependOn: 'shared',
            },
            shared: 'lodash',
        },
    
        optimization: {
            minimizer: [
                `...`,
                new CssMinimizerPlugin(),
            ],
            runtimeChunk: 'single',
        },

      一种是SplitChunksPlugin, 提取公共模块

       optimization: {
            minimizer: [
           `...`, new CssMinimizerPlugin(), ], splitChunks: { chunks: 'all', } },

      抽取出来的模块名是webapck默认的。 也可以手动控制打包分离

    splitChunks: {
        cacheGroups: {
            commons: {
                test: /[\\/]node_modules[\\/]/,
                name: "vendor",
                chunks: "all",
            },
        },
    },

      cache缓存

      文件名使用[contenthash], [name]-[contenthash].js. contenthash就是文件内容变,hash值就会变。output下的filename改为

    chunkFilename: "[name].[contenthash].js",
    filename: "[name].[contenthash].js",
    assetModuleFilename: "[name].[contenthash][ext][query]"

      chunkFilename,就是没有entry中定义,而生成的chunk的文件名,比如异步加载的文件,splitChunks生成的文件。filename 就是entry中定义的入口。assetModuleFilename: 就是图片等用assetmodule处理的文件。同时,css文件名

    new MiniCssExtractPlugin({
        filename: "[name].[contenthash].css",
      }),
      但有时,文件内容没有变化,conenthash仍然会有变化,因为在入口代码块中,webpack有特定的模板,特别是运行时和manifest,最好把这些模版抽取出来形成一个单独的代码块(chunk)。
    optimization 配置加一个 runtimeChunk: 'single' 配置
     optimization: {
         runtimeChunk: 'single',
       }

      此时如果添加一个模块,比如a.js,然后引入index.js,再打包,你会发现,所有的contenthash都发生变化,为什么呢?理论上,vendor不应该发生变化啊?这是因为module.ids增加了optimization.moduleIds 设为 'deterministic'

    optimization: {
         moduleIds: 'deterministic',
         runtimeChunk: 'single',    
    }

       整个optimization

    optimization: {
        moduleIds: 'deterministic',
        runtimeChunk: 'single', 
        minimizer: [
            `...`,
            new CssMinimizerPlugin(),
        ],
        splitChunks: {
            cacheGroups: {
                commons: {
                    test: /[\\/]node_modules[\\/]/,
                    name: "vendor",
                    chunks: "all",
                },
            },
        },
    
    },

      在一个典型的webpack打包的应用中,有三种代码,你写的代码,第三方库,webpack运行时runtime和manifest 。代码在浏览器中运行时,wepack 需要runtime 和manifest来链接模块,它们包含加载和解析模块的逻辑。当模块进行解析时,它来连接这些模块。已经下载到浏器中的模块,需要连接在一起,它才能执行。懒加载的模块需要解析。

      打包之后,整个应用变成了一个压缩文件,也有可能分为几个文件,webpack是怎么管理程序运行需要的模块之间的交互的呢?这就是menifest data的作用。当webpack进入,解析,打包你的应用时,它会记录所有module 详细的信息,这些信息的集合就是manifest。当打包后的文件时进入浏览器运行时,webpack runtime 就是依据这些信息来解析和加载模块。不论你使用什么模块语法,import 或require 都会变成__webpack_require__, __webpack_require__ 指向是模块的标识符。使用manifiest data,runtime能够知道向哪里去找标识符后面的module。

       resolve解析

      当webpack去查找一个模块时,如果是相对路径引入,它会转化成绝对路径,寻找模块的位置。如果是引入的第三方模块(模块路径),它会先到配置文件中的resolve.modules定义的目录去查找。默认情况下,resolve.module指的就是node_modules,就像下面的配置一样

    resolve: {
        modules: ['node_modules']
    }

      如果想让webpack去其它目录找第三言模块,则要把其它目录的路径配置到resolve.modules中,并且放在node_modules前面。.

    resolve: {
        modules: [path.resolve(__dirname, 'src/downloaded_libs'), 'node_modules']
    },

       使用resolve.alias可以创建alias路径来代替原始的模块路径

    resolve: {
        alias: {
            react: path.join(__dirname, 'node_modules/react'),
            'react-dom': path.join(__dirname, 'node_modules/react-dom')
        }
    }

      指定react和react-dom的别名路径,当webpack遇到react和react-dom时,它只会到当前项目的node_modules里面去找,可以解决npm link时, react 和react-dom 从不同的项目中获取的问题。alias也能创建文件别名路径 

    resolve: {
      alias: {
        CssFolder: path.resolve(__dirname, 'src/stylesheets/')
      }
    }

     import application from "../../../stylesheets/application.scss" 变成了 import application from "CssFolder/application.scss"

      通过alias和modules的配置,如果成功解析到路径,webpack就要看这个路径指向的是文件还是目录。如果是文件,并且有扩展名,文件就能找到了,如果没有扩展名,那就要找resolve.extensions中配置的扩展名。

    resolve: {
        extensions: ['.js', '.jsx']
    }

      如果路径指向的是一个目录,webpack先找package.json, 如果有,它会找resolve.mainFields定义的字段,

     resolve: {
        mainFields: ['browser', 'module', 'main'],
      },

      然后拿着这些字段到package.json去匹配,看package.json中有没有定义这些字段,第一个匹配成功的就是要找的文件的路径。如果没有pacakge.json或resolve.mainFields返回的是无效的路径,就要查找

      resolve.mainFiles定义的文件名,文件名如果没有扩展名,还是找resolve.extensions中配置的扩展名。
     
      webpack的工作过程

      webpack有一个入口文件,入口文件是一个module,它有可能通过import指向另外的module,另外的module又有可能指向另外的module,层层import,因此webpack在打包的的过程中,它也会根据这种import关系,遍历整个import, 构建出整个项目的依赖树,整个项目的依赖树构建完成,也就意味着,项目中所需要的代码都包含进来了,包含的过程中也对代码进行处理,生成打包文件。代码处理的依据就是配置文件。

       当import 一个文件,webpack会到文件目录中找这个模块,使用的是resolve 配置,resovle 就是调整模块解析算法的。 如果解析失败,会报错,如果解析成功,就会找对应的loader进行处理,loader处理完成后,就会把处理完后的代码放到打包文件中。在整个打包构建的过程中,webpack 会暴露一些event出来,webpack插件可以拦截这些event,做一些事情,比如抽离css到一个单独的文件。

       每一个模块都解析完之后,webpack就会输出打包文件。打包文件就是在浏览器中执行的webpack的运行时,还包括manifest,它列出了将要加载的打包文件。

      当然在打包过程,你还可以设置分离点,生成单独的包。

      在内部,webpack通过chunks 管理打包过程,chunks是一小段代码,这些代码包含在打包文件中,能够在webpack的输出中看到,输出的文件称为chunks。

      注意点

      webpack每打包一次就会生成一个[hash], 项目中只要有一个文件发生变化,webpack就需要重新打包,生成一个新的[hash], [hash]值就会发生变化,所有打包文件名字中[hash]值就会发生变化。

      [chunkhash],就是每一个chunk有一个hash. 比如有两个入口文件,一个是index.js,它import index.css, 一个admin.js, 它import admin.css. 如果进行打包, index.js和index.css是一个chunkhash,  admin.js 和admin.css是一个chunkhash, 但这两个chunkhash是不同的hash。如果此时改掉了index.css,那么重新打包后的index.js 和index.css 这两个文件都会有新的chunkhash, 文件名发生变化。

      contenthash, 为每一个文件单独计算hash值,哪个文件内容发生变化,打包时,哪个文件的对应的hash值就会发生变化。

      webpack打包速度提升

      1,安装分析插件:基于时间的分析工具speed-measure-webpack-plugin, 基于产物内容的分析工具webpack-bundler-analyzer

      2, 提升编译阶段的效率:a, 减小编译的模块,使用IgnorePlugin, 最典型的是moment包,它配置了多语言,实际上只用一个语言。

    module.exports = {
        // ...
        plugins: [
            // ...
            new webpack.IgnorePlugin(/^\.\/(?!zh-tw|en-gb)/, /moment[\/\\]locale$/)
        ]
    }

      安装引用类库的模块,比如引入loadash/es

      使用DLLPlugin:将项目所依赖的框架模块单独构建打包,与普通的模块打包区分开

        b, 提升单个模块的构建速度。include 和exclude,babel-loader。需要注意,优先使用exclude, 在include 和exclude 基础上noParse. ts编译,去掉检查

        c,并行构建:中小型项目,并行构建可以并不适应,因为多线程之间的通信,大型项目可以使用thread-loader,  parallel-webapck 

      3,提高打包过程中的效率: splunk chunk 和tree shaking: 以default模式引用的模块并不能tree shaking。引入单个对象的方式,无论是import * as ... 还是 import {...}, 都可以tree shaking. side effect ,babel 7 之后,module 选项是auto, 就是不会再把ESM转化成commonJS 了

      4,开启缓存,babel-loader缓存,使用cache-loader

      5 webpack5 持久化缓存:

       webpack 5 会跟踪每一个模块的依赖项,fileDependencies, ContextDependienices, missingDependencies, 当模块本身或其依赖项发生变化时,webpack 会找到所受影响的模块,重新进行构建。还可以配置失效。webpack5 会忽略插件的缓存设置,由引擎自身提供构建各环节的缓存的读写逻辑

  • 相关阅读:
    ERROR com.opensymphony.xwork2.interceptor.ParametersInterceptor
    vscode中使用node服务调试,会在promise的reject出现断点报错
    koa-router匹配多个路由添加中间件函数
    react-router中的路由钩子使用
    在less中不能正常使用css3的calc属性的解决方法
    react-redux安装失败的问题
    npm脚本命令npm run script的使用
    npx的使用和理解
    babel的命令行工具babel-cli解析
    babel的.babelrc解析
  • 原文地址:https://www.cnblogs.com/SamWeb/p/15914928.html
Copyright © 2020-2023  润新知