架构-webpack阶段笔记

1. webpack介绍
    本质上,webpack 是一个用于现代 JavaScript 应用程序的静态模块打包工具。当 webpack 处理应用程序时,它会在内部会以路口文件构建一个 依赖图(dependency graph),
    此依赖图对应映射到项目所需的每个模块,并生成一个或多个 bundle
    1)安装
        //webpack是核心包,webpack-cli命令行工具包,通过命令行工具包执行核心包
        npm install  webpack webpack-cli --save-dev
    2)核心概念
        1.入口(entry)
            入口起点指示 webpack 应该使用哪个模块,来作为构建其内部 依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的
            默认值是 ./src/index.js(./相对的目录是process.cwd(),就是运行的项目),但你可以通过在 webpack configuration 中配置 entry 属性,来指定一个(或多个)不同的入口起点        
        2.输出(output)
            output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件,主要输出文件的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中
            output: {
                path: path.resolve(__dirname, 'dist'),// 输出文件夹的绝对路径
                //入口代码块的名称 main
                filename: 'main.js' // 输出的文件名
                // 当你把打包后文件插入到index.html文件里的时候 src如何写的?publicPath+filename
                // /assets/main.js
                // publicPath:'/assets',//开发中要配publicPath:'/',不然引用资源路径前面没有/就是相对路径,可能资源路径会有问题                
                //非入口代码块的名称配置项 
                //非入口代码块有两个来源1.代码分割 vendor(第三方模块) common(共享模块)
                //懒加载  import方法加载模块
                chunkFilename:'[name].[hash:10].js',
            }
        3.loader
            webpack 只能理解 JavaScript 和 JSON 文件,loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效模块,以供应用程序使用,以及被添加到依赖图中,
            loader本质上是个转化器,可以把任意类型的模块转换成js模块,本质上是一个函数,接收源文件,返回一个JS模块代码 
            module: {
                rules: [
                    //raw-loader解析纯文本,直接将文本拿过来,什么事都不做
                  { test: /\.txt$/, use: 'raw-loader' }
                ]
              }
        4.插件(plugin)
            loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量
              plugins: [
                  //以./src/index.html为模板,将打包后的脚步插入到模板中
                new HtmlWebpackPlugin({template: './src/index.html'})
              ]
        5.模式mode
            日常的前端开发工作中,一般都会有两套构建环境
            一套开发时使用,构建结果用于本地开发调试,不进行代码压缩,打印 debug 信息,包含 sourcemap(方便代码调试)文件
            一套构建后的结果是直接应用于线上的,即代码都是压缩后,运行时不打印 debug 信息,静态文件不包括 sourcemap
            webpack 4.x 版本引入了 mode 的概念
            当你指定使用 production mode 时,默认会启用各种性能优化的功能,包括构建结果优化以及 webpack 运行性能优化
            而如果是 development mode 的话,则会开启 debug 工具,运行时打印详细的错误信息,以及更加快速的增量编译构建
            // mode 当前的运行模式  development开发环境  production生产环境 其默认值为 production
            mode: 'development',       
        6.webpack配置文件
            webpack配置文件是webpack.config.js文件,我们可以通过package.json文件中的"build": "webpack --config webpack.config2.js",
            来修改文件名,运行npm run build会自动去找webpack.config.js文件运行
2.开发环境配置
    webpack-dev-sever就是一个express服务器
    1)开发服务器webpack-dev-server的配置
        1.安装
            //可以帮我们预览我们的项目,可以支持热更新
            npm install webpack-dev-server --save-dev
        2.配置package.json文件指令
            //webpack5指令,webpack4指令是webpack-dev-server
            "start": "webpack serve"
        3.配置
            // devServer会启动一个HTTP开发服务器,把一个文件夹作为静态根目录,默认是output指定的文件夹,再是contentBase字段指定的文件夹
            // 静态文件根目录是可以有多个的,就是页面中访问的资源,如图片,会先去output指定的文件夹找,再去contentBase字段指定的文件夹找
            // 为了提高性能,使用的内存文件系统,不会再硬盘中输出dist目录,配置writeToDisk:true会在硬盘中输出一份
            // 开发环境使用,线上环境没用
            devServer: {
                contentBase: resolve(__dirname, 'static'),//指定静态根目录
                // writeToDisk:true,//如果你指定此选项,也会把打包后的文件写入硬盘一份
                compress: true, /// 是否启动压缩 gzip
                port: 8080, // 指定HTTP服务器的端口号
                open: true, // 自动打开浏览器
            },
    2)支持CSS
        1.安装模块
            //css-loader用来翻译处理@import和url()
            //style-loader可以把CSS生成style标签插入DOM中
            npm i style-loader css-loader -D
        2.配置
          module: {
            rules: [
              //rules匹配规则从下到上,从右到左
              { test: /\.css$/, use: ['style-loader','css-loader'] }
            ]
          },
    3)支持less和sass
        1.安装模块
            npm i less less-loader -D
            npm i node-sass sass-loader -D
        2.配置
            module: {
                rules: [
                  //先将less或sass转成css,再去解析@import和url(),再去把CSS生成style标签插入DOM中
                  { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] },
                  { test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] },
                ]
              },
    4)支持图片
        1.安装模块
            //file-loader 解决CSS等文件中的引入图片路径问题
            //url-loader 当图片小于limit的时候会把图片BASE64编码,大于limit参数的时候还是使用file-loader进行拷贝
            //html-loader 把html文件中引入资源的相对路径改成绝对路径,确保资源引入的正确性        
            npm i file-loader url-loader html-loader -D
        2.配置(一般四种都要配)
            1)方式1:放在静态文件static目录里,通过index.html中的img标签(/logo.png,会去static目录里找)直接引用,需要配置`devServer的contentBase字段`
            2)方式2:可以在CSS中通过  url 引入图片  css-loader来进行解析处理
            3)方式3:图片是通过import或require的方式引入的
             { test: /\.(jpg|png|bmp|gif|svg)$/, 
                use: [{
                    //如果使用file-loader那么没有limit字段,url-loader就是file-loader的加强版
                  loader: 'url-loader', 
                  options: {
                    //不开启es6模块,开启es6模块获取图片要加.default,而commonjs模块不需要
                    esModule: false,
                    //输出的图片名字,10位hash值加扩展名
                    name: '[hash:10].[ext]',
                    limit: 32 * 1024,// 如果文件的体积小于limit,小于8K的话,就转成BASE64字符串内嵌到HTML中,否则 行为和file-loader
                  }
                }]
             },
            4)方式4:html文件中通过相对路径的方式引入图片或其他资源
             //把html文件中引入资源的相对路径改成绝对路径             
             { test: /\.html$/, use: ['html-loader'] },
    5)JS的兼容性
        Babel其实是一个编译JavaScript的平台,可以把ES6/ES7,React的JSX转义为ES5
        1.babel-loader、babel-core、babel-preset-env之间的关系
            let babelCore = require('@babel/core');
            let presetEnv = require('@babel/preset-env');
            //babel-loader的本质就是个转换器,就是loader函数, 作用是调用babelCore
            //babelCore本身只是提供一个过程管理功能,把源代码转成抽象语法树,进行遍历和生成,它本身也并不知道 具体要转换什么语法,
            //以及语法如何转换,babel-preset-env做的就是这件事
            //1.先把ES6转成ES6语法树 babelCore
            //2.然后调用预设preset-env把ES6语法树转成ES5语法树 preset-env
            //3.再把ES5语法树重新生成es5代码 babelCore
            function loader(source){
                let es5 = babelCore.transform(source,
                    {
                        presets:['@babel/preset-env']
                    });
                return es5;
            }
            module.exports = loader;
        2.安装模块
            //babel-loader 使用Babel和webpack转译JavaScript文件
            //@babel/core Babel编译的核心包
            //@babel-preset-env 预设可以转换js语法
            //@babel/preset-react React插件的Babel预设,可以转换jsx语法
            //@babel/plugin-proposal-decorators把类和对象装饰器编译成ES5
            //@babel/plugin-proposal-class-properties转换静态类属性以及使用属性初始值化语法声明的属性
            //@babel/polyfill可以处理所有ES6以上的语法JS兼容包(不包含基本ES6语法),但是没有按需加载功能,需要配合core-js才有按需加载功能
            cnpm i babel-loader @babel/core @babel/preset-env @babel/preset-react  -D
            cnpm i @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties -D
            cnpm i @babel/polyfill    core-js -D
        3.配置
             {
                    test: /\.jsx?$/,
                    use: [
                        {
                            loader: 'babel-loader',
                            options: {
                                presets: [
                                    ["@babel/preset-env",//@babel/preset-env只能将一些普通ES6语法转换ES5一下,一旦一些较复杂的语法,如Promise、Async等,就不会转化(此时在IE浏览器运行就报错)
                                                        //@babel/polyfill可以处理所有ES6以上的语法JS兼容包(不包含基本ES6语法),但是没有按需加载功能,需要配合core-js才有按需加载功能
                                        /* {
                                                useBuiltIns: 'usage', // 按需加载polyfill,用到哪些高级的语法,就引入对应的代码来解析
                                                // debug: true,
                                                corejs: { version: 3 }, // 指定corejs(会去按需引入polyfill)的版本号 2或者3 
                                                targets: { // 指定要兼容哪些浏览器
                                                    chrome: '60',
                                                },
                                            }, */
                                    ],
                                    "@babel/preset-react", // 可以转换JSX语法
                                ],
                                //loader中的插件也是从下往上的,webpack的插件是从上往下的
                                plugins: [
                                    //legacy: true表示支持该插件,presets是plugins的集合
                                    ["@babel/plugin-proposal-decorators", { legacy: true }],
                                    ["@babel/plugin-proposal-class-properties", { loose: true }],
                                ],
                            },
                        },
                    ],
                },
        6)path的区别和联系
            |output|path|指定输出到硬盘上的目录
            |output|publicPath|表示的是打包生成的index.html文件里面引用资源的前缀
            |devServer|contentBase|用于配置提供额外(输出到硬盘上的目录额外的静态文件内容的目录)静态文件内容的目录
            |devServer|publicPath|表示的是打包生成的静态文件所在的位置,可以说是devServer设置了两个静态文件内容的目录,分别是contentBase和publicPath字段设置的,publicPath优先级更高
                                (若是devServer里面的publicPath没有设置,则会认为是output里面设置的publicPath的值,如果它也没有设置,就取默认值 `/`)
            - publicPath可以看作是对生成目录`dist`设置的虚拟目录(可以当做就是dist目录),devServer首先从devServer.publicPath中取值,如果它没有设置,就取 `output.publicPath`的值作为虚拟目录,如果它也没有设置,就取默认值 `/`
            - `output.publicPath`不仅可以影响虚拟目录的取值,也影响利用`html-webpack-plugin`插件生成的index.html中引用的js、css、img等资源的引用路径。会自动在资源路径前面追加设置的output.publicPath
            - 一般情况下都要保证`devServer`中的`publicPath`与`output.publicPath`保持一致
    6)ESLint代码校验
        1.安装模块
            //eslint核心包,babel-eslint转换一些高级语法让eslint可以识别
            cnpm install eslint eslint-loader babel-eslint --D
        2.创建配置文件.eslintrc.js
            module.exports = {
                root: true,
                parser:"babel-eslint",
                //指定解析器选项
                parserOptions: {
                    sourceType: "module",
                    ecmaVersion: 2015
                },
                //指定脚本的运行环境
                env: {
                    browser: true,
                },
                // 启用的规则及其各自的错误级别
                rules: {
                    "indent": "off",//缩进风格
                    "quotes":  "off",//引号类型 
                    "no-console": "error",//禁止使用console
                }
            }
        3.配置
             {
                test: /\.jsx?$/,
                loader: 'eslint-loader',  
                enforce: 'pre', // pre强制指定顺序,先进行代码校验,然后再编译代码         之前 pre normal inline post
                options: { fix: true }, // 启动自动修复
                include: resolve(__dirname, 'src'), // 只检查src目录里面的文件 白名单
                // exclude:/node_modules/ //不需要检查node_modules里面的代码 黑名单
            },
        4.配置保存代码自动修复eslint错误
            1)安装vscode的eslint插件
            2)src同级创建.vscode\settings.json文件
                {
                    "eslint.validate": [
                        "javascript",
                        "javascriptreact",
                        "typescript",
                        "typescriptreact"
                    ],
                    "editor.codeActionsOnSave": {
                        "source.fixAll.eslint": true
                    }
                  }
    7)sourcemap(代码调试用的)
        sourcemap可以解决在浏览器运行代码时报错,定位报错代码的源文件位置(生成的map文件是给浏览器使用的)
        webpack通过配置可以自动给我们source maps文件,map文件是一种对应编译文件和源文件的方法
        1.配置webpack的devtool字段
            //false表示什么都没做
            //source-map(使用它就行,包含最完整的信息)包含行和列信息,包含loader的sourcemap(包含loader可以定位到loader编译前的文件就是源文件,不包含就是定位到编译后的文件)
            //inline-source-map包含行和列信息,包含loader的sourcemap,不会单独生成一个map文件,会内嵌到编译后的文件中
            //cheap-source-map包含行信息不包含列信息,不包含loader的sourcemap
            //cheap-module-source-map包含行信息不包含列信息,包含loader的sourcemap
            //开发环境推荐使用cheap-module-eval-source-map,线上推荐使用hidden-source-map
            devtool: 'false',
    8)打包第三方类库
        1.方式1:通过import或require直接引入
            //只能在引入文件中使用
            import _ from 'lodash';
        2.方式2:插件引入
            webpack配置ProvidePlugin后,在使用时将不再需要import和require进行引入,直接使用即可
            _ 函数会自动添加到模块的上下文,无需显示声明(只能在模块中使用,不能在全局中使用,必如index.html文件中)
            const webpack = require('webpack');
            plugins: [
                  //这样就可以在所有模块中使用_,但是不能在全局中使用,必如index.html文件中
                  new webpack.ProvidePlugin({
                      _: 'lodash',
                  }), 
            ],
        3.expose-loader
            expose-loader可以把模块添加到全局对象上,在调试的时候比较有用
            1)在一个模块中引入第三方模块
                import _ from 'lodash';
            2)配置webpack,顺便把引入的模块添加到全局上
                  module: {
                    rules: [
                      {
                          test: require.resolve('lodash'),
                          loader: 'expose-loader',
                          options: {
                              exposes: {
                                  //模块中使用的名字
                                  globalName: '_',
                                  override: true,
                              },
                          },
                      }
                    ]
                  }
        4.cdn方式引入
            如果我们想引用一个库,但是又不想让webpack打包,并且又不影响我们在程序中以CMD、AMD或者window/global全局等方式进行使用,那就可以通过配置externals
            1)配置webpack
                npm i html-webpack-externals-plugin -D
                const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');
                new HtmlWebpackExternalsPlugin({
                    externals: [
                        {
                            module: 'lodash', // 模块名
                            //cdn地址
                            entry: "https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.20/lodash.js",
                            global: '_', // 全局变量名
                        },
                    ],
                }),
            2)文件中使用
                const _ = require("lodash");
    9)设置环境变量
        1.方式一
            在package.json文件中设置
            "scripts": {
                "build": "webpack --env=production",
            },
            在webpack.config.js文件中获取
            module.exports = (env) => {
                console.log(env.production)//结果为true
            }
        2.方式二
            npm i cross-env
            在package.json文件中设置
            "scripts": {
                "build": "cross-env NODE_ENV=production webpack",
              },
            在webpack.config.js文件中获取
            console.log(process.env.NODE_ENV);
    10)定义全局变量
        const webpack = require('webpack');
        //可以在其它模块中直接使用process.env.NODE_ENV
        new webpack.DefinePlugin({
            "process.env.NODE_ENV": mode,
        }), 
    11)环境变量的区别
        三个变量 一个是在模块内部使用的变量默认production,一个是在node环境也就是webpack.config.js里面用的变量默认undefined,都是通过process.env.NODE_ENV来获取
        一个是webpack配置文件中导出的函数参数env默认undefined,模块中获取的process.env.NODE_ENV就是webpack的mode字段值,默认是production
        1.如何修改模块中的process.env.NODE_ENV(这个变量是webpack内部通过webpack.DefinePlugin插件设置全局变量process.env.NODE_ENV为mode的值)
            //在package.json文件中配置
            "build": "webpack --mode=development"
        2.如何修改webpack配置文件中导出的函数参数env
            //在package.json文件中配置
            "build": "webpack --env=development"
        3.如何让模块中process.env.NODE_ENV的值等于webpack配置文件中导出的函数参数env
            const webpack = require('webpack');
            //方式1
            //定义全局process.env.NODE_ENV覆盖模块中的process.env.NODE_ENV
            new webpack.DefinePlugin({
                "process.env.NODE_ENV": env.development?JSON.stringfy('development'):JSON.stringfy('production'),
            }), 
            //方式2
            mode:env.development?development:production
        4.如何修改webpack.config.js里面用的process.env.NODE_ENV
            //cross-env可以保证在多个系统里面设置环境变量是一样的
            npm i cross-env
            //在package.json文件中设置
            "scripts": {
                "build": "cross-env NODE_ENV=production webpack",
              },
    12)调试代码
        1.测试环境调试(这个时候要把webpack配置文件的devtool字段设为false)
            1)下载模块
                npm i filemanager-webpack-plugin -D
            2)配置
                 new webpack.SourceMapDevToolPlugin({
                    filename:'[file].map',//定义打包生成的 source map 的名称 main.js.map
                    append:"\n//# sourceMappingURL=http://localhost:8081/[url]" //在源文件中增加sourcemap文件的映射
                }),
                new FileManagerPlugin({
                    events:{
                        onEnd:{
                            copy:[
                                {//source表示将打包后dist目录中的map文件放到destination指定的目录中,delete表示再把dist目录中的map文件删除
                                    source:'./dist/*.map',
                                    destination:'C:/aproject/zhufengwebpack202011/1.basic/sourcemap'
                                }
                            ],
                            delete:['./dist/*.map']
                        }
                    }
                }) 
        2.生产环境调试
            webpack打包仍然生成sourceMap,但是将map文件挑出放到本地服务器,将不含有map文件的部署到服务器,跟测试环境调试一样,就是不配置append就行
    13)watch、clean、copy、proxy
        1.watch
            当代码发生修改后可以自动重新编译
            1)配置
                watch:true,//开启监听
                watchOptions:{//监听选项
                    ignored:/node_modules/,//不监听哪些文件夹
                    aggregateTimeout:300,//监听到文件发生变化后延迟300毫秒才去重新编译(相当于防抖)
                    poll:1000//1秒扫描1000次文件,数字越大,越敏感,数字越小,越延迟
                }, 
        2.copy
            打包时,将项目中的文件直接拷贝到dist目录中,不经过编译
                1)安装模块
                    npm i copy-webpack-plugin -D
                2)配置
                    const CopyWebpackPlugin = require('copy-webpack-plugin');
                    new CopyWebpackPlugin({
                        patterns:[
                            {//from来源,to拷贝去哪里
                                from:resolve(__dirname,'src/documents'),
                                to:resolve(__dirname,'dist/documents')
                            }
                        ]
                    }),     
        3.clean
            可以打包前先清空dist目录
            1)安装模块
                npm i  clean-webpack-plugin -D
            2)配置
                const {CleanWebpackPlugin} = require('clean-webpack-plugin');
                new CleanWebpackPlugin({
                     // **/*代表清空所有文件
                    cleanOnceBeforeBuildPatterns:["**/*"]
                }),、
        4.proxy
            //访问8080的请求并且以/api开头的,如http://localhost:8080/api/users
            //会被转发到http://localhost:3000/users,重写路径去掉/api
            devServer: {
                port: 8080, // 指定HTTP服务器的端口号
                proxy:{
                    '/api':{
                        target:'http://localhost:3000',
                         pathRewrite:{
                            "^/api":""
                        } 
                    }
                } */
            },
    14)chunk和bundle和module
        打包前一个入口和它依赖的模块是一个chunk,打包后就变成一个bundle(一个是打包前叫法,一个是打包后叫法)
        module就是一个模块文件,一个 chunk 应该包括多个 module

3.生产环境
    1)提取CSS
        因为CSS的下载和JS可以并行,当一个HTML文件很大的时候,我们可以把CSS单独提取出来使用link标签引入并行加载,提高加载效率,原来的css是通过style标签脚步插入到
        html文件中的,style标签引入的css不能并行加载,加载效率低
        1.下载模块
            npm install --save-dev mini-css-extract-plugin
        2.配置
            const MiniCssExtractPlugin = require('mini-css-extract-plugin')
            //用MiniCssExtractPlugin.loader替换掉style-loader(这个loader会把css以style脚步的形式插入到页面)
            //MiniCssExtractPlugin.loader会把所有的css样式先收集起来
            { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] },
            //把收集到的所有的CSS样式都写入到css/main.css,然后HtmlWebpackPlugin插件把此资源插入到HTML里去
            new MiniCssExtractPlugin({
                filename:'css/[name].css'
            })
    2)如何打包后的文件分类放好,图片放images文件夹,css放css文件夹里面
        1.图片
            {
                test: /\.(jpg|png|gif|bmp)$/,
                use: [{
                    loader: 'url-loader',
                    options: {
                        name: 'images/[hash:10].[ext]',
                        esModule: false,
                        limit: 32 * 1024,
                    },
                }],
            },
                              或
            {
                test: /\.(jpg|png|gif|bmp)$/,
                use: [{
                    loader: 'url-loader',
                    options: {
                        name: '[hash:10].[ext]',
                        esModule: false,
                        limit: 32 * 1024,
                        //html文件中的引入路径是output中的publicPath+这里的publicPath+/+name
                        outputPath:'images',//指定输出图片的目录images目录 
                        publicPath:'/images'//访问图片的话也需要去images目录里找
                    },
                }],
            },
        2.css
            const MiniCssExtractPlugin = require('mini-css-extract-plugin')
            //用MiniCssExtractPlugin.loader替换掉style-loader(这个loader会把css以style脚步的形式插入到页面)
            //把所有的css样式先收集起来
            { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] },
            //把收集到的所有的CSS样式都写入到css/main.css,然后HtmlWebpackPlugin插件把此资源插入到HTML里去
            new MiniCssExtractPlugin({
                filename:'css/[name].css'
            })   
    3)hash、chunkhash和contenthash(实战,打包出来的文件名会根据个人需求添加这三个hash,webpack再次打包时对比的hash值一样不在重新打包,直接走缓存)
        文件指纹是指打包后输出的文件名和后缀,hash一般是结合CDN缓存来使用,通过webpack构建之后,生成对应文件名自动带上对应的MD5值。如果文件内容改变的话,那么对应文件哈希值也会改变,
        对应的HTML引用的URL地址也会改变,触发CDN服务器从源服务器上拉取对应数据,进而更新本地缓存。     
        指纹占位符:    
            ext    资源后缀名
            name    文件名称
            path    文件的相对路径
            folder    文件所在的文件夹
            hash    每次webpack构建会根据整个项目的模块内容生成一个统一的hash值
            chunkhash    每次webpack构建会根据同一个chunk的所有的模块生成一个chunkhash,来源于同一个chunk,则hash值就一样
            contenthash    每次webpack构建会根据模块内容生成一个contenthash,文件内容相同hash值就相同
    4)webpack打包文件的文件名的由来
        1.对于入口来说,name就是entry的key,字符串就是main
        2.对于非入口来说 
              import('./src/title.js'):是根据相对路径计算来的把/和.换成_,如src_title_js
              代码分割 vendor common名字自己指定的
    5)CSS兼容性
        为了浏览器的兼容性,有时候我们必须加入-webkit,-ms,-o,-moz这些前缀
        Trident内核:主要代表为IE浏览器, 前缀为-ms
        Gecko内核:主要代表为Firefox, 前缀为-moz
        Presto内核:主要代表为Opera, 前缀为-o
        Webkit内核:产要代表为Chrome和Safari, 前缀为-webkit
        1.安装模块
            //postcss-loader可以使用PostCSS处理CSS
            //postcss-preset-env把现代的CSS转换成大多数浏览器能理解的
            npm i postcss-loader postcss-preset-env -D
        2.方式一
            配置postcss.config.js文件
                let postcssPresetEnv = require('postcss-preset-env');
                module.exports={
                    plugins:[postcssPresetEnv()]
                }
            配置webpack
                { test: /\.less$/, use: ['style-loader', 'css-loader','postcss-loader', 'less-loader'] },
        3.方式二
            配置webpack
             { test: /\.css$/, use: [
                'style-loader', 
                'css-loader',
                {
                    loader:'postcss-loader',
                    options:{
                        postcssOptions: {
                            plugins: [
                                "postcss-preset-env"
                            ],
                        },
                    }
            },
    6)压缩JS、CSS和HTML
        optimize-css-assets-webpack-plugin是一个优化和压缩CSS资源的插件
        terser-webpack-plugin是一个优化和压缩JS资源的插件
        html-webpack-plugin 压缩html
        1.配置
            const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
            const TerserPlugin = require('terser-webpack-plugin');
            const HtmlWebpackPlugin = require('html-webpack-plugin');
            optimization:{
                minimize:process.env.NODE_ENV==='production',//如果是生产环境才开启压缩
                //mode=production,就不需要再自己配置TerserPlugin,内部自己会启动terser-plugin进行压缩
                //mode=development,还要想压缩,就得配置TerserPlugin
                minimizer:(env&&env.production)?[
                    new TerserPlugin()//如果是生产环境才会配置js压缩器
                ]:[]//否则不配置任何压缩器
            },
            plugins: [
                new HtmlWebpackPlugin({
                template: './src/index.html',
                    minify:{//启动HTML压缩
                        collapseWhitespace:true,//去掉空格
                        removeComments:true//去掉注释
                    }
                }),
                (env&&env.production)&&new OptimizeCssAssetsWebpackPlugin()
                //上面可能得到false,去掉false,否则报错
            ].filter(Boolean)
            
    7)px 自动转成rem
        1.下载模块
            //px2rem-loader自动将px转换为rem
            npm i px2rem-loader lib-flexible -D
        2.配置
            webpack配置
            { test: /\.css$/, use: [
                'style-loader', 
                'css-loader',
                {
                    loader:'postcss-loader',
                    options:{
                        postcssOptions: {
                            plugins: [
                                "postcss-preset-env"
                            ],
                        },
                    }
            },
            {
                loader:'px2rem-loader',
                options:{
                    //75px转成1rem
                    remUnit:75
                }
            }] },
            index.html文件中
            <script>
              let docEle = document.documentElement;
              function setRemUnit () {
                //750/10=75   375/10=37.5
                docEle.style.fontSize = docEle.clientWidth / 10 + 'px';
              }
              setRemUnit();
              window.addEventListener('resize', setRemUnit);
            </script>
    8)多入口配置
        1.一般会创建一个文件夹来放所有路口文件(本项目是pages文件夹)
        2.动态配置入口和输出页面
            //basename根据文件名取出后缀,拿到文件名前缀
            const { resolve ,join,basename} = require('path');
            const fs = require('fs');
            const HtmlWebpackPlugin = require('html-webpack-plugin');
            //动态生成多路口配置,和生成多个html文件配置
            let pagesRoot = resolve(__dirname,'src','pages');
            let pages = fs.readdirSync(pagesRoot);
            let htmlWebpackPlugins = [];
            let entry = pages.reduce((entry,fileName)=>{
                //entryName='page1'
                let entryName = basename(fileName,'.js');
                entry[entryName] = join(pagesRoot,fileName);
                htmlWebpackPlugins.push(new HtmlWebpackPlugin({
                    template: './src/index.html',
                    //输出的多个html文件,设置page1为默认入口页面
                    filename:`${entryName==='page1'?'index':entryName}.html`,
                    //将那个入口文件插入到html文件中
                    chunks:[entryName],
                    minify:{//启动HTML压缩
                        collapseWhitespace:true,
                        removeComments:true
                    }
                }));
                return entry;
            },{});
            plugins: [
                ...htmlWebpackPlugins]
    9)合并多个webpack配置
        1.安装模块
            npm i webpack-merge -D
        2.使用(我们一般会在config文件夹里面写所有的webpack配置)
            webpack.dev.js文件中
                const { merge } = require('webpack-merge');
                const base = require('./webpack.base');
                const devConfig = {
                    mode:'development',
                };
                module.exports = merge(base,devConfig);
            webpack.prod.js文件中
                webpack.dev.js文件中
                const { merge } = require('webpack-merge');
                const base = require('./webpack.base');
                const prodConfig = {
                    mode:'production',
                };
                module.exports = merge(base,prodConfig);
4.原理
    1)预备知识
        1.Symbol.toStringTag
            Symbol.toStringTag 是一个内置 symbol,它通常作为对象的属性键使用,对应的属性值应该为字符串类型,这个字符串用来表示该对象的自定义类型标签
            通常只有内置的 Object.prototype.toString() 方法会去读取这个标签并把它包含在自己的返回值里
            let myExports = {};
            Object.defineProperty(myExports, Symbol.toStringTag, { value: "人" });
            console.log(Object.prototype.toString.call(myExports)); //[object 人]
        2.模块的ID
            - 不管你是用什么样的路径来加载的,最终模块ID统一会变成相对根目录的相对路径
              - index.js ./src/index.js
              - title.js  ./src/title.js
              - jquery  ./node_modules/jquery/dist/jquery.js    
    2)源码
        1.同步加载打包文件
            1.sync/main.js文件中将所有项目的依赖模块放在modules对象上,箭头函数自调用执行主入口文件代码,调用require()方法会先去cache对象中读取缓存
            没有缓存就会去modules对象上兑取模块内容
        2.模块兼容性实现实现
            1)common.js 加载common.js
                webpack自己实现了一个common.js,所有天生支持common.js 加载common.js
            2)common.js 加载 ES6 modules
                2.common-load-es/main.js文件中modules对象中,如果引入的模块是es6方式导出的会调用require.r()说明这个模块是es6模块
                会调用require.d()方法将默认导出的模块放在exports.default属性上,分别导出的模块直接放在exports上
            3)ES6 modules 加载 ES6 modules
                3.es-load-es/main.js文件中使用require()方法加载主入口文件,在modules对象中增加主入口模块,对主入口模块做上面2)的处理
            4)ES6 modules 加载 common.js
                4.es-load-common/main.js文件中,./src/title.js模块是common.js模块不做处理,./src/index.js模块把import语法变成require语法
                同个require.n()获取引入模块默认值
            5)总结
                1.把es6转成common,被引入的es6会给exports.default添加模块默认导出模块,会给exports添加分别默认导出模块
                2.引入的es6模块的imort会变成require,使用的默认值会通过require.n()获取,使用的分别值会通过引入对象.的方式获取
        3.异步加载代码块
            1)0到4步做了通过jsonp异步加载导入的模块
            2)5步收集hello.js模块里面的所有模块,6步将hello.js的所有模块挂在全局modules并让promise成功,接着走then(require.bind(require, "./src/hello.js"))
                 步骤加载hello模块内容
        4.AST的生成和遍历
            1)抽象语法树定义
              webpack和Lint这些工具的原理都是通过JavaScript Parser把代码转化为一颗抽象语法树(AST),这颗树定义了代码的结构,通过操纵这颗树,我们可以精准的定位到声明语句、赋值语句、
                 运算语句等等,实现对代码的分析、优化、变更等操作;
            2)JavaScript Parser
              JavaScript Parser是把JavaScript源码转化为抽象语法树的解析器。
                 浏览器会把JavaScript源码通过解析器转为抽象语法树,再进一步转化为字节码或直接生成机器码。一般来说每个JavaScript引擎都会有自己的抽象语法树格式,
              Chrome 的 v8 引擎,firefox 的 SpiderMonkey 引擎等等,MDN 提供了详细SpiderMonkey AST format 的详细说明,算是业界的标准。
            3)常用的 JavaScript Parser
                esprima
                traceur
                acorn
                shift
            4)下载模块
                //esprima把JS源代码转成AST语法树
                //estraverse遍历语法树,修改树上的节点
                //escodegen把AST语法树重新转换成代码
                cnpm i esprima estraverse escodegen -S
        5.转换箭头函数babel插件
            1)下载模块
                //@babel/core  Babel的编译器,用来生成语法树,遍历语法树调用预设插件修改语法上的节点,生成新的代码
                //babel-types 用于 AST 节点的 Lodash 式工具库, 它包含了构造、验证以及变换 AST 节点的方法,对编写处理 AST 逻辑非常有用
                npm i @babel/core babel-types -D
            2)在arrow.js文件中调用core.transform()方法生成语法树,遍历语法树时调用预设插件修改语法上的节点,生成解析后的新代码,解析箭头函数
                 时会调用BabelPluginTransformEs2015ArrowFunctions插件的ArrowFunctionExpression()方法将箭头节点传给该方法,方法中
                 将节点类型变成es5函数类型
            3)调用hoistFunctionEnvironment()方法处理this指针的问题,如果箭头函数内部有用到this,那么会在外层作用域定义let _this = this
                 然后把箭头函数里面的this变量换成 _this
            4)总结
                1.调用core.transform()方法解析代码生成ast语法树,遍历语法树调用预设的插件转换高级语法生成新的代码,插件实际上是个对象(plugins字段中是类),对象上的visitor属性
                     放在各种解析高级语法的方法
                2.@babel/preset-env插件上就集合了一些转换es6语法的插件,一些高级es6语法需要预设其它插件来解析
        6.类转换
            npm i @babel/plugin-transform-classes
            1)转换前
                class Person {
                    constructor(name) {
                      this.name = name;
                    }
                    getName() {
                      return this.name;
                    }
                }
            2)转换后
                function Person(name){
                    this.name = name;
                }
                Person.prototype.getName = function(){
                    return this.name;
                } 
            3)在class.js文件中ClassDeclaration()方法获取原来类的方法,包括构造方法和普通方法,这个遍历处理方法,如果处理构造方法,那么创建es5类
                 复用构造方法的参数和函数体,如果处理普通方法,那么给es5类原型上加上该方法即可
        7.编写插件的一般步骤
            1) 仔细观察转换前和转换后的语法树,找到它们的相同点和不同点
            2) 想办法把转换前的转成转换后的,并且要尽可能和复用旧节点(老的没有,新的有,就得创建新节点了,可以通过babel-types可以创建新节点)
        8.tree-shaking插件(babel-plugin-import这个插件实现了)     
            1)转换前
                import {flatten,concat} from 'lodash';
            2)转换后(转换前会把整个包引进来,转换后只会引入使用的包)
                import flatten from 'lodash/flatten';
                import concat from 'lodash/concat';
    3)tapable
        tapable 是一个类似于 Node.js 中的 EventEmitter的库,但更专注于自定义事件的触发和处理
        webpack 通过 tapable 将实现与流程解耦,所有具体实现通过插件的形式存在
        let {SyncHook} = require('tapable')
        //不同的事件需要创建不同的hook
        //优点就是结构会比较清晰
        //webpack事件大概有四五百种,有几百个钩子,各干各的监听 和触发,互不干扰
        //需要给构建函数传给一个形参数组,它将决定在call的时候要接收多少个参数
        let aHook = new SyncHook(['a','age']);
        let bHook = new SyncHook(['b','age']);
        //tap类似于我们以前学的events库中的 on  监听事件
        aHook.tap('这个名字没有什么用,只是给程序员看的',(name,age)=>{
            console.log(name,age,'这是一个回调');
        });
        //call类似于我们以前学的events库中的 emit
        aHook.call('zhufeng',10);
    4)webpack 编译流程(debug.js文件中实现)
        1.在webpack/index.js文件中的webpack()方法中初始化参数:从配置文件(webpack.config.js)和Shell语句(cmd窗口输入的命令)中读取并合并参数,得出最终的配置对象
             用上一步得到的参数初始化Compiler对象,加载所有配置的插件,插件中的apply()方法的compiler.hooks.run.tap()订阅钩子,Compiler对象中的run()方法执行编译过程中
             去执行对应的钩子来改变编译结果   
        2.在debug.js文件中执行compiler对象的run()方法开始执行编译(插件的执行顺序:不同的hook,触发的顺序就是webpack触发hooks的顺序
             同一个hook,就是注册的顺序)
        3.Compiler.js文件中的run()方法中根据配置中的entry找出入口文件entryFilePath
        4.Compiler.js文件中的run()方法中的buildModule()从入口文件出发,调用所有配置的Loader对模块进行编译得到targetSourceCode
          在通过parser.parse()方法得到ast树,在通过traverse()方法遍历语法树,并找出require节点,修改我们的ast树,同时module对象
          搜集模块id和它依赖的模块数组,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
        5.Compiler.js文件中的run()方法中根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,放到this.chunks上
             再把每个Chunk转换成一个单独的文件加入到输出列表this.assets上
        6.Compiler.js文件中的run()方法中在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
        7.总结
            1)初始化参数:从配置文件和Shell语句(sricpt脚步命令)中读取并合并参数,得出最终的配置对象
            2)用上一步得到的参数初始化Compiler对象
            3)加载所有配置的插件
            4)执行对象的run方法开始执行编译
            5)根据配置中的entry找出入口文件
            6)从入口文件出发,调用所有配置的Loader对模块进行编译
            7)再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
            8)根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk
            9)再把每个Chunk转换成一个单独的文件加入到输出列表
            10)在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
        
5.loder
    1)特点
        1.所谓 loader 只是一个导出为函数的 JavaScript 模块。它接收上一个 loader 产生的结果或者资源文件(resource file)作为入参。
        2.compiler(编译) 需要得到最后一个 loader 产生的处理结果。这个处理结果应该是 String 或者 Buffer,因为这样webpack才能将字符串js脚本转成ast语法树
        3.loader 的执行顺序 = pre(前置)+normal(正常)+inline(内联)+post(后置) ,执行顺序从后往前,pre、normal、post在webpack.config.js文件中通过enforce字段配置
            // inline在引入文件是配置let request = `inline-loader1!inline-loader2!${filePath}`;
            // !号前面就是loader
            {
                test:/\.js$/,
                enforce:'post', //默认normal
                use:['post-loader1','post-loader2']
            },
        4.loader-runner是一个执行loader链条的的模块,webpack内置loader,先从前往后执行loader中的pitch方法,再去读取文件,再去
             从后往前执行loder,如果pitch方法中有返回值,那么直接将返回值给前一个patch的loader
    2.特殊符号配置
        1)-!noPreAutoLoaders
            // inline在引入文件是配置let request = `-!inline-loader1!inline-loader2!${filePath}`;
            // 不要前置和普通 loader
            // 在reqire方法前面使用
        2)!noAutoLoaders
            //     不要普通 loader
        3)!!noPrePostAutoLoaders
            // 不要前后置和普通 loader,只要内联 loader
    3.babel-loader
        在babel-loader.js文件中调用@babel/core和设置options中的presets将高级js代码转成es5 js代码
    4.file-loader
        负责把图片打包到dist目录中
    5.url-loader
        没有超出设置的limit将图片转成base64内嵌的html中,超出的话交给file-loader去处理
    6.less-loader、css-loader、style-loader
        less-loader把LESS编译成CSS字符串、css-loader的作用是处理css中的@import 和 url(./images/logo.png)
        style-loader把CSS变成一个JS脚本,脚本就是动态创建一个style标签,并且把这个style标签插入到HTML里header 
    7.loader-runner
        1)loader-runner.js文件中runLoaders()方法获取配置对象的资源路径、读取资源的方法、处理资源的loader、loader的上下文对象loaderContext
        2)通过createLoaderObject()方法返回loader对象数组,给上下文对象loaderContext添加一些必要属性
        3)调用iteratePitchingLoaders()方法循环执行loader的pitch方法,如果pitch方法中有返回值,那么直接调用iterateNormalLoaders()方法调用
              前一个patch的loader,如果pitch没有返回值都执行完调用processResource()方法读取文件 ,文件读取完后调用 iterateNormalLoaders()方法循环执行loader
        4)总结
            先从前往后执行loader中的pitch方法,再去读取文件,再去从后往前执行loder,如果pitch方法中有返回值,那么直接将返回值给前一个patch的loader
    8.css-loader
        @import and url()翻译成import/require(),然后可以解析处理它们
        1)css-loader处理过后的webpack
            1.dist/main3.js文件中api.js模块返回了一个list数组,并且重写了数组的toSring()方法拿到css模块内容
            2.css-loader.js!./src/global.css模块返回数组EXPORT包含所有的css模块id和css内容
            3../src/global.css模块返回所有的css模块内容
            4.在main4.js文件中css-loader.js!./src/index.css模块导出所有css模块数组,@import导入的其它css模块通过require()方法引入
                 在通过EXPORT.i(GLOBAL)将css模块添加到总模块当中
        2)css-loader
            1)PostCSS(相当于js中的babel)是一个将CSS代码转成语法树的工具,再去遍历操作css节点,px-to-rem的实现原理就是这样的
            2)css-loader.js文件中pipeline.process()方法会将css代码转成语法树交给cssPlugin(),cssPlugin()会把@import干掉再将引入的css路径
              放到options.imports里,删完@import的代码会在pipeline.process().then()的result中拿到
            3)pipeline.process().then()返回js脚本,脚本中将所有的@import变成了require,本模块内容放在list中,在webpack解析require是会循环此
                 步骤,最终所有的css模块内容都会被整合到list中
            4)通过importLoaders属性处理模块id,就是require()路径前缀,表示当前模块需要经过哪些loader处理
            5)处理url(),cssPlugin会把url(xxx)变成url(require('xxx')),require会给webpack看和分析,webpack一看你引入了一张图片
              webpack会使用file-loader去加载图片
        3)style-loader
            在style-loader的pitch()方法中创建style标签,并把css插入标签中插入html文件中,其它css loader会内联到require中
            
6.插件
    插件的apply()方法会订阅回调在webpack的编译对象compiler.hooks上,在编译的过程中会去触发对应的回调去改变webpack编译结果
    1)tapable 分类
        Webpack 本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是 Tapable,webpack 中最核心的负责编译的 Compiler 
        和负责创建 bundle 的 Compilation 都是 Tapable 的实例
        1.按同步异步分类
            Hook 类型可以分为同步Sync和异步Async,异步又分为并行和串行
            Sync:SyncHook,SyncBailHook,SyncWaterfallHook,SyncLoopHook
            Async:AsyncParallelHook(并行),AsyncParallelBailHook,AsyncSeriesHook(串行),AsyncSeriesBailHook,AsyncSeriesWaterfallHook
        2.按返回值分类
            BasicL:执行每一个事件函数,不关心函数的返回值,有 SyncHook、AsyncParallelHook、AsyncSeriesHook
            Bail:执行每一个事件函数,遇到第一个结果 result !== undefined 则返回,不再继续执行。有:SyncBailHook、AsyncSeriesBailHook, AsyncParallelBailHook
            Waterfall:如果前一个事件函数的结果 result !== undefined,则 result 会作为后一个事件函数的第一个参数,有 SyncWaterfallHook,AsyncSeriesWaterfallHook
            Loop:不停的循环执行事件函数,直到所有函数结果 result === undefined,有 SyncLoopHook 和 AsyncSeriesLoopHook
    2)SyncHook
        const {SyncHook} = require('tapable');
        // 从上往下依次调用
        // ['name','age']定义回调的参数个数,与变量名无关
        const hook = new SyncHook(['name','age']);
        // 订阅事件,参数1没有意义,给人看的
        hook.tap('1',(name,age)=>{
            console.log(1,name,age);
        });
        // 触发事件
        hook.call('jiagou',20);
    3)SyncBailHook
        const {SyncBailHook} = require('tapable');
        //当回调函数返回非undefined的值 的时候会停止后续调用
        const clickHook = new SyncBailHook(['name','age']);        
        clickHook.tap('1',(name,age)=>{
            console.log(1,name,age);
        });
        //后面再有clickHook的事件不执行
        clickHook.tap('2',(name,age)=>{
            console.log(2,name,age);
            return null;
        });
    4)SyncWaterfallHook
        表示如果上一个回调函数返回结果不为undefined,则会作为下面回调函数的第一个参数,否则按传的
        const {SyncWaterfallHook} = require('tapable');
        const clickHook = new SyncWaterfallHook(['name','age']);    
        clickHook.tap('1',(name,age)=>{
            console.log(1,name,age);
            return 'jiagou';        
        });
        clickHook.tap('2',(name,age)=>{
            console.log(2,name,age);
        });
    5)SyncLoopHook
        不停的循环执行回调函数,直到所有函数的结果等于undefined,特别要注意是每次循环都是从头开始的
    6)AsyncParallelHook
        异步并行执行所有的订阅回调,都执行完后会执行callAsync的回调
        const { AsyncParallelHook } = require('tapable');
        const hook = new AsyncParallelHook(['name', 'age']);
        1.方式1
            hook.tapAsync('1', (name, age, callback) => {
                setTimeout(() => {
                    console.log(1, name, age);
                    callback();
                }, 1000);
            });
            hook.tapAsync('2', (name, age,callback) => {
                setTimeout(() => {
                    console.log(2, name, age);
                    callback();
                }, 2000);
            });
            hook.callAsync('zhufeng', 10, (err) => {});
        2.方式2
            hook.tapPromise('1', (name, age) => {
               return new Promise(function(resolve){
                    setTimeout(() => {
                        console.log(1, name, age);
                        resolve();
                    }, 1000);
               });
            });
            hook.tapPromise('2', (name, age) => {
                return new Promise(function(resolve){
                    setTimeout(() => {
                        console.log(2, name, age);
                        resolve();
                    }, 2000);
               });
            });
            hook.promise('zhufeng', 10).then((result) => {});
    7)AsyncParallelBailHook
        异步并行执行所有的订阅回调,有一个任务返回值不为空就直接结束,对于promise来说,就是resolve或reject的值不为空
        对于callback来说,就是callback有参数
    8)AsyncSeriesHook
        异步串行执行所有的订阅回调,都执行完后会执行callAsync的回调
    9)AsyncSeriesBailHook
        异步串行执行所有的订阅回调,有一个任务返回值不为空就直接结束,对于promise来说,就是resolve或reject的值不为空
        对于callback来说,就是callback有参数
    10)AsyncSeriesWaterfallHook
        异步串行执行所有的订阅回调,如果上一个回调函数返回结果不为undefined,则会作为下面回调函数的第一个参数,否则按传的
    11)stage和before
        let {SyncHook} = require('./tapable');
        let hook = new SyncHook(["name"]);
        // 默认回调从上到下执行,stage可以控制执行顺序,越小越先执行
        hook.tap({name:'tap1',stage:2},(name)=>{
            console.log('tap1',name);
        });
        hook.tap({name:'tap3',stage:1},(name)=>{
            console.log('tap3',name);
        });
        // before可以控制回调在哪些回调之前执行,优先级比stage高
        hook.tap({name:'tap1'},(name)=>{
            console.log('tap1',name);
        });
        hook.tap({name:'tap2',before:['tap1']},(name)=>{
            console.log('tap2',name);
        });
        hook.call('zhufeng');
    12)自定义AutoExternalPlugin插件
        通过cdn的方式引入第三方库,webpack中可以通过externals字段配置
        
7.hmr热更新
    模块内容发生变化会重写打包调用方模块文件,页面只会刷新改变的区域
    1. 启动一个HTTP服务器,会打包我们的项目,并且让我们可以预览我们的产出的文件,默认端口号8080
    2. 还会启动一个websocket双向通信服务器,如果有新的模块发生变更的话,会通过消息的方式通知客户端,让客户端拉取最新代码,并且进行客户端的热更新 
    //webpack配置
    devServer:{
        hot:true,//支持热更新,webpack会自动帮你添加HotModuleReplacementPlugin插件
    },
    // 模块中./title.js模块发生变化会调用render()方法
    let render = ()=>{
        let title = require('./title.js');
        document.getElementById('root').innerText = title;
    }
    render();
    if(module.hot){
        //可以接收并处理哪些模块的变更
        module.hot.accept(["./title.js"],render);
    }
    
8.模块联邦
    Module Federation的动机是为了不同开发小组间共同开发一个或者多个应用,每个应用块由不同的组开发
    应用或应用块共享其他其他组件或者库
    // host主机,消费方
    let ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
     new ModuleFederationPlugin({
        name:'host',
        filename:'remoteEntry.js',
        remotes:{ //消费的组件
            remote:'remote@http://localhost:3000/remoteEntry.js'
        }
    })
    import React from 'react';
    const RemoteNewsList = React.lazy(()=>import('remote/NewsList'));
    export default (props)=>{
        return (
            <div>
                <h1>远程的NewsList</h1>
                <React.Suspense fallback="loading RemoteNewsList">
                  <RemoteNewsList/>
                </React.Suspense>
            </div>
        )
    }
    // remote远程,提供方
    let ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
    //如果这个组件想被别人引用,引用路径 ${name}/${expose}  remote/NewsList
    new ModuleFederationPlugin({
        name:'remote',
        filename:'remoteEntry.js',
        exposes:{
            './NewsList':'./src/NewsList'
        }
    })
    
9.webpack5新特性介绍
    1)安装
        npm install webpack webpack-cli webpack-dev-server html-webpack-plugin babel-loader @babel/core  @babel/preset-env @babel/preset-react style-loader css-loader --save-dev
        npm install react react-dom --save
    2)启动命令
        "start": "webpack serve"
    3)持久化缓存(内置了cache配置)
        webpack会缓存生成的webpack模块和chunk,来改善构建速度
        缓存在webpack5中默认开启,缓存默认是在内存里,但可以对cache进行设置
        webpack 追踪了每个模块的依赖,并创建了文件系统快照。此快照会与真实文件系统进行比较,当检测到差异时,将触发对应模块的重新构建
        cache: {
           // 缓存地方 filesystem硬盘持久化缓存    memory内存
           // 默认是memory
           type: 'filesystem',  //'memory' | 'filesystem'
           // 缓存的文件放在那里,filesystem才要配置,下面是默认值
           cacheDirectory: path.resolve(__dirname, 'node_modules/.cache/webpack'),
           },
    4)资源模块 (webpack5里file-loaer url-loader已经废弃 了,直接设置type:'asset')
        资源模块是一种模块类型,它允许使用资源文件(字体,图标等)而无需配置额外 loader
        raw-loader => asset/source 导出资源的源代码
        file-loader => asset/resource 发送一个单独的文件并导出 URL
        url-loader => asset/inline 导出一个资源的 data URI
        asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现
        {
            test:/\.png$/,
            type:'asset/resource'//对标file-loader
        },
        {
            test:/\.ico$/,
            type:'asset/inline'//对标url-loader 模块的大小<limit base64字符串
        },
        {
            test:/\.txt$/,
            type:'asset/source'//对标raw-loader
        },
        {
            test:/\.jpg$/,
            type:'asset',//对标raw-loader,大于4*1024走file-loader,小于4*1024走url-loader
            parser:{
                dataUrlCondition:{
                    maxSize:4*1024
                }
            }
        },
    5)moduleIds & chunkIds的优化
        module: 每一个文件其实都可以看成一个 module
        chunk: 每个入口对应一个chunk
        在webpack5之前,没有从entry打包的chunk文件,都会以1、2、3...的文件命名方式输出,删除某些些文件可能会导致缓存失效
        +   optimization:{
        +       moduleIds:'deterministic',
        +       chunkIds:'deterministic' // deterministic文件名以模块文件名为基础生成的hash值,文件名不变缓存就不会失效,因为是取hash值前三位,所以打包出来的文件超过999个会有命名冲突风险
        +   }
    6)移除Node.js的polyfill
        webpack4带了许多Node.js核心模块的polyfill,一旦模块中使用了任何核心模块(如crypto),这些模块就会被自动启用
        webpack5不再自动引入这些polyfill,需要自己配置
        cnpm i crypto-js crypto-browserify stream-browserify buffer -D
        resolve:{
            /* fallback:{ 
                "crypto": require.resolve("crypto-browserify"), // 模块的polyfill,不需要可以配置false
                "buffer": require.resolve("buffer"),
                "stream":require.resolve("stream-browserify")
            }, */
        },
    7)更强大的tree-shaking
        tree-shaking就在打包的时候剔除没有用到的代码
        webpack4 本身的 tree shaking 比较简单,主要是找一个 import 进来的变量是否在这个模块内出现过
        webpack5可以进行根据作用域之间的关系来进行优化
        // webpack配置
        optimization:{
            usedExports:true //标使用到的导出,开发环境下会使用tree-shaking
        }
        // package.json文件配置
        // tree-shaking可能会有副作用,所有的css文件不使用tree-shaking
        "sideEffects":["*.css"],
    8)模块联邦
        
        

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值