一、模块化开发 Commonjs 规范 
Commonjs 是以同步模式加载模块 
一个文件就是一个模块 
每个模块都有单独的作用域 
通过module.exports导出成员 
通过require函数载入模块 
 
ES Module ESM 是 ES6 引入的 JS 标准模块化规范,它的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系和输入和输出的变量。
功能 模块功能主要由两个命令构成:export和import。 export 命令用于规定模块的对外接口, import 命令用于输入其他模块提供的功能。
特性 通过给 script 添加 type = module 的属性,就可以以 ES Module 的标准执行其中的 js 代码了
自动采用严格模式,忽略‘use strict’ 
每个 ES Module 模块都是单独的私有作用域 
ES Module 是通过 CORS 去请求外部 js 模块的,服务器要支持 CORS 
ES Module 的 script 标签会延迟执行脚本(页面渲染完在执行脚本) 
 
export 导出 
导出
导出成员使用 export 修饰成员声明的方式,如: 
 
 
 
1 2 export  var  name = 'yan; export var age = 18' 
 
1 2 3 var  name = "yan" ;var  age = 18 ;export  { name, age };
 
使用 as 给导出的成员重命名,导入时就要使用重命名后的变量名,如: 
 
1 2 var  name = "yan" ;export  { name as  firstName };
 
如果重命名为 default 那这个成员就会做为当前模块默认导出的成员 
在导入这个成员就必须要给这个成员重命名,default 是一个关键词不能作为一个变量使用 
导入
导入使用 import 导入,如:import { name } from './module.js',name 为导出的变量名 
导入默认导出传成员可以不使用花括号,变量名可以根据需要随便取,如:import name from './module.js' 
如果导入的变量名为 default,需要使用 as 重命名,如:import { default as firstName } from './module.js' 
 
 
 
导入导出的注意事项 
export { name, age } 该写法不是对象字面量的,如果要对象字面量的写法应该是:export default { name, age },这样花括号会被理解为对象 
import {} from './module.js',花括号不是解构 
导出成员时,导出的是这个成员的引用,导出的变量是只读的(在导入的文件中无法改变变量的值),如,在导出成员的文件中在一秒后改变变量的值,在导入变量的文件中两秒后输出变量值,输出的结果是改变后的值 
 
1 2 3 4 5 6 7 8 9 10 export  var  a = 1 ;setTimeout (() =>  {  a = 2 ; }, 1000 ); import  { a } from  "./module.js" ;setTimeout (() =>  {  console .log (a);  }, 2000 ); 
 
import 导入用法 
import 必须使用完整的路径不能省略扩展名,即使是引入 index 也要完整的路径不能省略 index 
import 可以使用完整的 url 加载模块 import { name } from 'https://xxx.com/xxx.js' 
相对路径,在网页引用资源是可以省略‘./’的,但是 import 不可以省略,如果省略了就是以字母开头 ES Module 会以为在加载第三方模块,可以使用绝对路径和完整的 url 
如果想要执行某个模块而不用提取模块成员,花括号为空即可,这是就只会执行这个模块,不会提出成员,如:import {} from './module.js',简写:import './module.js' 
如果模块导出的成员特别多,而导入的都用到,可以使用星号‘_’,把这个模块的成员全部导入,在通过 as 将导入的成员全部放到一个对象中,导出的成员都会作为这个对象的属性,如:import _ as mod from './module.js' 
动态导入模块,不可以通过判断使用if (true){import {name} from './module.js'},如果需要动态导入,可以使用 import 函数import('./module.js'),import 函数返回的是一个 promise 
如果模块同时导出默认成员和命名成员,在导入时可以给默认成员重命名import { name, default as age } from './module.js',也可以import age(默认成员), { name } from './module.js' 
 
导出导入成员 
import 可以配合 export 使用,效果就是将导入的结果直接作为当前模块的导出成员,那么在当前作用域就不能访问这些成员,如:export { name, age } from './module',利用这种特性可以把散落的模块组织在一起,如,一个 index 把零散的模块组织一起,然后再导出 
 
1 2 3 4 5 6 7 8 9 10 var  Button  = "Button Component" ;export  default  Button ;export  var  Avatar  = "Avatar Component" ;export  { default  as  Button  } from  "./button.js" ;export  { Avatar  } from  "./avatar.js" ;import  { Button , Avatar  } from  "./index.mjs" ;
 
ES Modules 浏览器环境 Polyfill 
在不支持 ES Module 的浏览器中,可以使用 browser-es-module-loader 第三方库,在执行的时候解析 
但是在支持 ES Module 的浏览器中,这样会执行两次,可以添加 script 标签的新属性 nomodule 
nomodule:在不支持 ES Module 的浏览器工作 
 
1 2 3 4 5 6 7 8 9 10 11 12 <script    nomodule    src ="https://unpkg.com/promise-polyfill@8.1.3/dist/polyfill.min.js"  > </script > <script    nomodule    src ="https://unpkg.com/browser-es-module-loader@0.4.1/dist/babel-browser-build.js"  > </script > <script    nomodule    src ="https://unpkg.com/browser-es-module-loader@0.4.1/dist/browser-es-module-loader.js"  > </script > 
 
ES Modules in Node.js - 支持情况 
在 node 中使用 ES Module 文件后缀名为‘mjs’ 
执行 node --experimental-modules 文件,experimental-modules:启用 ES Module 实验特性 
可以通过 import 导入原生的模块和第三方模块import fs from 'fs'; import _ form 'loadsh' 
import { camelCase } from 'lodash',不支持,因为第三方模块都是导出默认成员;内置模块兼容了 ES Module 的提取成员方式,所以import { writeFileSync } from 'fs'支持 
 
ES Modules in Node.js - 与 CommonJS 交互 
ES Module 中可以导入 Commonjs 模块 
Commonjs 中不能导入 ES Module 模块 
Commonjs 始终只会导出一个默认成员 
注意 import 不是解构导出对象 
 
ES Modules in Node.js - 与 CommonJS 的差异 
ES Module 中没有 Commonjs 中的那些模块全局成员(require/module/exports/**filename/**dirname) 
Commonjs 中 require/module/exports 在 ES Module 中使用 import 和 export 替代,**filename 和 **dirname 可以通过import.meta.url 可以拿到当前工作文件的文件 url 地址,可以使用原生的 url 模块的fileURLToPath()方法可以把文件 url 转换为路径 
 
1 2 3 4 import  { fileURLToPath } from  "url" ;import  { dirname } from  "path" ;const  __filename = fileURLToPath (import .meta .url );const  __dirname = dirname (__filename);
 
ES Modules in Node.js - 新版本进一步支持 
在 package.json 文件添加 type 字段值为 module,此时文件扩展名可以用 js,而不需要 mjs 
此时想要使用 Commonjs 规范,那么文件后缀名要改为 cjs 
 
ES Modules in Node.js - Babel 兼容方案 
安装 babel 相关依赖 yarn add @babel/node @babel/core @babel/preset-env babel 工作依赖插件,preset 是一个插件集合 
执行 yarn babel-node index.js(想要转换的文件) --presets=@babel/preset-env 
或者添加一个.babelrc 文件 
babel 的核心就是 preset-env,用于将 ES6 转换成 ES5 代码。 
 
1 2 3 {   "presets" :  [ "@babel/preset-env" ]  } 
 
二、Webpack 打包 模块打包工具的由来 
ES Module 存在环境兼容问题 
模块文件过多,网络请求频繁 
所有的前端资源都需要模块化 
 
Webpack 配置文件 
webpack 默认以src/index.js 为入口文件 
在项目的根目录添加webpack.config.js文件,可以自定义相应的配置,该文件是运行在 nodejs 环境中 
该文件导出一个对象,通过导出对象的属性就可以完成相应的配置选项 
entry 属性:webpack 打包入口文件的路径,如果是相对路径‘./’不能省略 
output 属性:webpack 打包输出的位置,值要求是一个对象,通过对象的 filename 属性指定输出的文件名,path 属性指定输出的目录(绝对路径) 
 
Webpack 工作模式 
在 webpack.config.js 文件对象中添加 mode 属性 
mode 默认值是 production,mode 可选值有 production、development、none 
production:自动启动优化,会把代码压缩加密 
development:会自动优化打包速度,添加一些调式过程中的辅助 
none:运行最原始的打包,不做额外的处理 
 
loader webpack 的核心就是 loader。
Webpack 资源模块加载 
webpack 内容默认只会处理 js 文件 
处理其他类型的文件要安装使用对角的 loader(加载器) 
loader 的输入和输出都是字符串. 
使用 loader,在webpack.config.js导出的对象中的 module 属性,添加一个 rules 数组 
rules 数组就是针对加载其他资源模块的规则配置 
每个规则对象要设置两个属性,
test:正则表达式,匹配打包过程中遇到的文件; 
use:匹配到的文件使用的 loader,use 的值可以是单个 loader 字符串也可以是一个数组 loader,如果是一个数组,会从右往左执行  
 
 
css-loader 的作用就是将 css 文件转换为一个 js 模块;具体实现就是将 css 代码 push 到一个数组中 
style-loader 的作用就是将 css-loader 转换后的结果通过 style 标签追加到页面上 
loader 是 webpack 的核心特性 
借助不同的 loader 就可以加载任何类型的资源 
 
file-loader 文件资源加载器 
安装依赖 yarn add file-loader 
在配置文件中添加 rules { test: /.png$/, use: 'file-loader' } 
webpack 默认所有打包的结果放在网站根目录;为项目中的所有资源指定一个基础路径,在 output 中添加 publicPath 属性,默认值为空表示网站的根目录 
把 publicPath 值修改为 ‘dist/‘ 注意“/”不能省略,打包后是一个变量名(publicPath 值)拼接资源名(资源名前面没有“/”)所有“/”不能省略 
 
publicPath是整个项目的静态资源的基础路径,一般在生产模式下使用.
而webpack-dev-server中的publicPath是打包后代码存放的路径.这里的文件访问的静态资源路径还是output中的publicPath.所以二者要保持一致.
假设 output.publicPath 设置成了 './dist/',那么打包后的 JS 引用路径为 ./dist/main.js。这里会存在一个问题,相对路径在本地能正常访问到。但是如果把静态资源托管到 CDN 上,访问路径显然不能使用相对路径的。如果设置成 '/dist/',则打包后的访问路径是 localhost:8080/dist/main.js,此时本地无法访问。 一般解决方法就是利用 webpack.DefinePlugin 来定义一个全局变量(process.env.NODE_ENV)区分开发、生产环境来设定不同的值,或者是采用两份不同的配置文件来进行打包。 一般来说,output.publicPath 应该以 ‘/‘ 结尾,而其他 loader 或插件的配置不要以 ‘/‘ 开头。
url-loader URL 加载器 优点: 不用再发起 http 请求
Data URLS 格式:data(协议):[mediatype(媒体类型和编码)][;base64], 
安装插件 yarn add url-loader --dev 
在配置文件中添加 rules 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 module .exports  = {  module : {     use : [       {         test : /.png$/ ,         use : {           loader : "url-loader" ,           option : {             limit : 10  * 1024 ,                         },         },       },     ],   }, }; 
 
loader 中做大小限制还是需要安装 file-loader,不然无法解析.
最佳实践
小文件使用 Data URLs,减少请求次数 
大文件单独提取存放,提高加载速度 
 
Webpack 常用加载器 loader 分类 
编译转换类:会把加载的资源转为 js 代码,如css-loader 
文件操作类:会把加载的资源拷贝到输出的目录,导出文件访问路径,如file-loader 
代码检查类:目的是统一代码风格,如eslint-loader 
 
Webpack 与 ES6 wepack 并不能转换 es6 中的其他特性,只能处理模块相关的import,export
安装babel-loader @babel/core @babel/preset-env 
在配置文件中 module rules 添加 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 module .exports  = {  module : {     rules : [       {         test : /.js$/ ,         use : {           loader : "babel-loader" ,           options : {             presets : ["@babel/preset-env" ],           },         },       },     ],   }, }; 
 
Webpack 加载资源的方式 
遵循 ES Modules 标准的 import 声明 
遵循 Commonjs 标准的 require 函数 
遵循 AMD 标准的 define 函数和 require 函数 
Loader 加载器的非 JavaScript 也会触发资源加载
样式代码中的@import 指令和 url 函数 
HTML 代码中图片标签的 src 属性 
 
 
 
如果用require导致 esm 模块代码,需要用导入值的default属性导入.不过,除非必要,不要混用.
1 const  heading = require ("./heading.js" ).default ;
 
Webpack 核心工作原理 
webpack 会根据配置找到打包入口文件,然后顺着入口文件的代码 
根据代码中的 import 或者 require 解析推断这个文件依赖的资源模块 
然后分别解析每个资源模块对应的依赖,形成整个项目中所有用到文件之间的依赖关系的依赖树 
webpack 递归依赖树,找到每个节点对于的资源文件 
根据配置文件中的 rules 属性找到模块对应的加载器,找到对于的资源放在打包结果中,实现整个项目的打包 
 
Webpack 开发一个 Loader loader 都需要导出一个函数,这个函数就是对加载资源的处理.
loader 负责资源文件从输入到输出的转换 
对于同一个资源可以依次使用多个 loader (css-loader -> style-loader) 
loader 模块导出一个函数;该函数输入的是加载到的资源文件内容;函数体是处理内容的过程;输出的是经过处理过的资源文件内容结果,结果必须是 javascript 代码 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import  about from  "./about.md" ;console .log (about);const  marked = require ("marked" );module .exports  = (source ) =>  {              const  html = marked.parse (source);         return  html; }; module .exports  = {  mode : "none" ,   entry : "./src/main.js" ,   output : {     filename : "bundle.js" ,     path : path.join (__dirname, "dist" ),     publicPath : "dist/" ,   },   module : {     rules : [       {         test : /.md$/ ,         use : ["html-loader" , "./markdown-loader" ],       },     ],   }, }; 
 
plugin loader 实现资源加载,plugin 解决除了资源加载,其他的自动化工作. 比如,打包前清除 dist 目录(为了清除上一次代码),文件压缩,拷贝静态文件到输出目录. 也就实现了大部分的前端工程化的工作.
plugin 插件机制介绍 
增强 webpack 自动化能力 
loader 专注实现资源模块加载 
plugin 解决其他自动化工作
清除 dist 目录 
拷贝静态文件至输出目录 
压缩输出代码 
 
 
 
plugin 之 自动清除输出目录插件 
安装插件 yarn add clean-webpack-plugin --dev 
配置文件中导入插件 
在配置对象中 plugins 属性,该属性是专门配置插件的地方,属性值是一个数组 
数组里就是导入的类型创建的实例. 
 
1 2 3 4 const  { CleanWebpackPlugin  } = require ("clean-webpack-plugin" );module .exports  = {  plugins : [new  CleanWebpackPlugin ()], }; 
 
plugin 之 自动生成 HTML 插件 
安装插件 yarn add html-webpack-plugin --dev 
在配置文件中导入插件 
在配置对象中 plugins 属性,该属性是专门配置插件的地方,属性值是一个数组 
同时输出多个页面文件,可以再通过 HtmlWebpackPlugin 生成多个实例,通过 filename 指定文件名 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const  HtmlWebpackPlugin  = require ("html-webpack-plugin" );module .exports  = {  plugins : [          new  HtmlWebpackPlugin ({       title : "Webpack Plugin Sample" ,        meta : {         viewport : "width=device-width" ,        },       template : "./src/index.html" ,      }),          new  HtmlWebpackPlugin ({       filename : "about.html" ,     }),   ], }; 
 
plugin 之 复制静态文件 
安装插件 yarn add copy-webpack-plugin --dev 
 
有些文件不需要打包,比如网站图标 icon,可以使用copy-webpack-plugin直接复制.
建议上线前再使用此插件,否则频繁打包会影响性能,平时开发可以使用的devserver中的contentBase属性设置静态资源路径
1 2 3 new  CopyWebpackPlugin ([  'public'  ]) 
 
plugin 之 开发一个插件 原理: 钩子机制,通过在生命周期的钩子中挂载函数实现的.
一般就是定义一个类,在类中定义 apply 方法,使用的时候就是通过这个类去构建实例.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class  MyPlugin  {     apply (compiler ) {          console .log ("my plugin 启动" );                    compiler.hooks .emit .tap ("MyPlugin" , (compilation ) =>  {              for  (const  name in  compilation.assets ) {                                    if  (name.endsWith (".js" )) {                      const  contents = compilation.assets [name].source ();            const  withoutComments = contents.replace (/\/\*\*+\*\//g , "" );            compilation.assets [name] = {                          source : () =>  withoutComments,              size : () =>  withoutComments.length ,            };         }       }     });   } } 
 
webpack-dev-server 集成了自动编译,自动刷新浏览器功能
1 2 3 4 5 #  安装 pnpm add webpack-dev-server -D #  运行 pnpm webpack-dev-server #  如果加--open会自动打开浏览器 
 
Webpack Dev Server 静态资源访问 
Dev Server 默认只会 serve 打包输出文件 
只要是 webpack 打包输出的文件都可以直接被访问 
 
1 2 3 4 devServer : {     contentBase : "./public" ; } 
 
Webpack Dev Server 代理 API 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 module .exports  = {  devServer : {     proxy : {              "/api" : {                  target : "https://api.github.com" ,                  pathRewrite : {           "^/api" : "" ,          },                  changeOrigin : true ,       },     },   }, }; 
 
Source Map Source Map 介绍 
调试和报错都是基于运行代码 
source map 映射转换后代码和源代码关系,通过 source map 逆向解析 
解决源代码与运行代码不一致所产生的调试问题 
 
Webpack 配置 Source Map 在配置项添加 devtool: 'source-map'
Webpack eval 模式的 Source Map 
eval 是否使用 eval 执行模块代码,带 eval,用 eval 执行模块代码,只能定位文件,不能定位具体位置. 
eval-source-map 可以定位具体位置 
cheap-eval-source-map 可以定位行,不能定位列. 
cheap-source map 是否包含行信息 
module 是否能够得到 Loader 处理之前的源代码 
带 module 的模式下解析的出来的原代码是没有经过 loader 加工,手写的源代码 
不带 module 是 loader 加工过后的代码 
inline-source-map:source map 是因 dataURL 嵌入代码,其他的 source map 是以物理文件存在 
hidden-source-map:开发工具看不到 source map 效果 在开发第三方包比较有用 
 
Webpack 选择 Source Map 模式 
开发模式
cheap-module-eval-source-map:代码每行不会超过 80 个字符;代码进过 loader 转换后差异较大需要 module 模式(loader 处理前的源代码);首次打包速度慢无所谓,重写打包相对较快 
 
 
生成模式
none或nosources-source-map:Source Map 会暴露源代码 
 
 
 
热更新 HMR Webpack HMR 体验(热更新) 
应用运行过程中实时替换某个模块,应用运行状态不受影响 
HMR 集成在webpack-dev-server中 
开启 HMR
css 样式(经过 loader 处理的)可以热更新但是 js 还是会刷新浏览器,webpack 中的 HMR 不可以开箱即用 
webpack 中的 HMR 需要手动处理模块热替换逻辑 
 
 
 
1 2 3 4 5 6 7 8 const  webpack = require ("webpack" );module .exports  = {  target : "web" ,   devServer : {     hot : true ,   },   plugins : [new  webpack.HotModuleReplacementPlugin ()], }; 
 
Webpack 使用 HMR API Webpack 处理 JS 模块热替换 js 热更新:保存旧数据,代码发生变化后把数据回填
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let  lastEditor = editor;module .hot .accept ("./editor" , () =>  {        const  value = lastEditor.innerHTML ;   document .removeChild (editor);   const  newEditor = createEditor ();   newEditor.innerHTML  = value;   document .body .appendChild (newEditor);   lastEditor = newEditor; }); module .hot .accept ("./better.png" , () =>  {  img.src  = background;   console .log (background); }); 
 
HMR 注意事项 
如果没有开启热更新,但是 js 代码做了热更新处理,先做判断 if (module.hot){// 热更新逻辑} 
hot 会刷新浏览器,报错信息会被刷走;hotOnly不会刷新浏览器,可以把错误信息输出 
打包后会自动去除 js 热更新逻辑代码 
 
Webpack 生产环境优化 
mode:none、production、development 
为不同的工作环境创建不同的配置 
 
Webpack 不同环境下的配置 
1 2 3 4 5 6 7 8 9 10 module .exports  = (env, argv ) =>  {  const  config = {        };   if  (env === "production" ) {        } }; 
 
一个环境对应一个配置文件
webpack.common.js 存放公共配置 
webpack.dev.js 开发配置,把公共配置和开发配置合并(使用 loadsh merge) 
webpack.prod.js 生产环境,把公共配置和生产配置合并(使用 loadsh merge) 
使用webpack-merge插件替换 loadsh 工具. 
在没有默认配置文件的情况就要执行 pnpm webpack --config webpack.dev.js 
或者在 scripts 中添加命令 "build": "webpack --config webpack.dev.js" 
 
 
 
1 2 3 4 5 const  common = require ('./webpack.common' )const  merge = require ('webpack-merge' )module .exports  = merge (common, {  ... } 
 
Webpack DefinePlugin 
DefinePlugin 为代码注入全局成员 
接收的是一个对象,每一个键值都会注入到代码中 
文件代码中直接使用键,打包后把注入的值 直接替换到代码中,definePlugin 传递的字符串应该是代码 
 
1 2 3 4 5 6 7 8 9 10 11 12 const  webpack = require ("webpack" );module .exports  = {  plugins : [     new  webpack.DefinePlugin ({                     API_BASE_URL : '"https://api.github.com"' ,     }),   ], }; console .log (API_BASE_URL );
 
Tree Shaking tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)
在生成模式下自动开启,对于冗余、未引用代码不会打包 
不是指某个配置选项,是一组功能搭配使用后的优化效果 
只支持 ESM,不支持 CommonJS.(ESM 是静态引入,cms 是动态引入) 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 module .exports  = {  mode : "none" ,   entry : "./src/index.js" ,   output : {     filename : "bundle.js" ,   },      optimization : {     usedExports : true ,      minimize : true ,    }, }; 
 
Webpack 合并模块—concatenateModules 
尽可能的将所有模块合并输出到一个函数中 
提升运行效率,减少代码体积 
 
1 2 3 4 5 6 7 8 9 10 11 module .exports  = {  mode : "none" ,   entry : "./src/index.js" ,   output : {     filename : "bundle.js" ,   },      optimization : {     concatenateModules : true ,    }, }; 
 
Webpack Tree Shaking 与 Babel 
babel 和 tree shaking 同时使用会导致 tree shaking 失效
tree shaking 前提是 ES Modules,由 webpack 打包的代码必须使用 ESM 
babel-loader 转换代码时可能会把 ES Modules 转为 CommonJS 新版的 babel-loader 不会导致 tree shaking 失效, 
 
 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 module .exports  = {  module : {     rules : [       {         test : /\.js$/ ,         use : {           loader : "babel-loader" ,           options : {             presets : [                              ["@babel/preset-env" , { modules : "commonjs"  }],              ],           },         },       },     ],   }, }; 
 
sideEffects 
允许通过配置方式标识代码是否有副作用(模块执行时除了导出成员之外所做的事情) 
一般用于开发 npm 包时 标记是否有副作用 
生产模式自动开启 会检查当前代码所属的 package.json 有没有 sideEffects 标识,以此来判断这个模块是否有副作用 
如果这个模块没有副作用,那些没有用到的模块不会被打包 
package.json 添加 sideEffects: false 表示 package.json 所影响的项目当中所有的代码都没有副作用 
 
1 2 3 optimization : {  sideEffects : true ; } 
 
Webpack sideEffects 注意 
使用 sideEffects 前提确保代码真的没有副作用,否则 webpack 再打报时会误删有副作用的代码 
如在 Number 原型添加方法然后载入、载入 css 也是 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Number .prototype  .pad  = function (size ) {  let  result = this  + ''    while (result.length  < size) {     result = '0'  + result   }   return  result } import  './extend.js' console .log (8. pad (3 ))
 
解决:在 package.json 关闭副作用(设置为 false)或者标识哪些文件有副作用 
 
1 2 3 4 5 "sideEffects" : [   './src/extends.js' ,   '*.css'  ] 
 
代码分割 
所有代码最终都被打包到一起,打包文件体积过大 
分包,按需加载 
多入口打包 
动态导入 
 
Webpack 多入口打包 
多页应用程序,一个页面对应一个打包入口,公共部分单独提取 
多个入口可以把 entry 定义为对象,一个属性就是一个入口,键就是入口名,值就是入口对应的文件路径 
多入口,输出也要多个,可以给 filename 的值添加 [name]占位 outpur:{filename: '[name].bundle.js'} 这样 [name]就会被替换为入口名称 
plugins 中的 HtmlWebpackPlugin 插件会自动输入注入所有打包结果的 html,所以此时入口文件会注入所有的打包结果 
可以给 HtmlWebpackPlugin 插件配置注入的打包文件 new HtmlWebpackPlugin({chunks:['index']}), new HtmlWebpackPlugin({chunks:['album']}) 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 const  { CleanWebpackPlugin  } = require ("clean-webpack-plugin" );const  HtmlWebpackPlugin  = require ("html-webpack-plugin" );module .exports  = {  mode : "none" ,   entry : {     index : "./src/index.js" ,     album : "./src/album.js" ,   },   output : {     filename : "[name].bundle.js" ,   },   module : {     rules : [       {         test : /\.css$/ ,         use : ["style-loader" , "css-loader" ],       },     ],   },   plugins : [     new  CleanWebpackPlugin (),     new  HtmlWebpackPlugin ({       title : "Multi Entry" ,       template : "./src/index.html" ,       filename : "index.html" ,       chunks : ["index" ],     }),     new  HtmlWebpackPlugin ({       title : "Multi Entry" ,       template : "./src/album.html" ,       filename : "album.html" ,       chunks : ["album" ],     }),   ], }; 
 
Webpack 提取公共模块 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 module .exports  = {     optimization : {     splitChunks : {              chunks : "all" ,              minSize : 20000 ,              minChunks : 1 ,              cacheGroups : {                  defaultVendors : {           test : /[\\/]node_modules[\\/]/ ,           name : "vendors" ,           priority : -10 ,         },         default : {                      minChunks : 2 ,           name : "default" ,           priority : -20 ,         },       },     },   },    }; 
 
Webpack 动态导入 
需要用到某个模块时,再加载这个模块 
动态导入的模块会被自动分包 
一般我们都是在文件头直接 import 文件的,这会造成资源浪费 
可以使用 ES Modules 的 import()在需要导入的地方导入模块,该方法返回的是 promise 可以拿到导出的对象 
 
1 2 3 4 5 if  (true ) {  import ("./posts" ).then (({ default : posts } ) =>  {     mainElement.append (posts ());   }); } 
 
Webpack 魔法注释 
在 import()方法中添加特定的注释 import(/* webpackChunkName: 'posts' */ './posts') 这样在打包后文件名就会带上这个 webpackChunkName 值 
如果 webpackChunkName 相同,打包后会打包在一起 
 
安装插件 yarn add mini-css-extract-plugin --dev 
 
注意: 如果 css 小于 150k,就不要用这个了,不然单独生成一个文件,就多次请求,效果可能不如style-loader.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const  MiniCssExtractPlugin  = require ("mini-css-extract-plugin" );module .exports  = {  module : {     rules : [       {         test : /\.css$/ ,         use : [                      MiniCssExtractPlugin .loader ,            "css-loader" ,         ],       },     ],   },   plugins : [new  MiniCssExtractPlugin ()], }; 
 
Webpack OptimizeCssAssetsWebpackPlugin— 压缩 css 
安装插件 yarn add optimize-css-assets-webpack-plugin --dev 
如果配置在 plugins 中这个插件在任何情况都会正常工作 
配置在 minimizer 中 只有 minimizer 开启(生产环境自动开启)是才工作 
webpack 建议压缩创建配置在 minimizer 中,以便通过 minimizer 选项统一控制 
当 minimizer 使用一个数组,webpack 认为我们要自定义所使用的压缩器插件,webpack 内部的 js 压缩器就会被覆盖,如果不配置 js 压缩器,这是打包,js 代码不会被压缩 
手动把 js 压缩器添加回来,安装插件 yarn add terser-webpack-plugin --dev 
 
1 2 3 4 5 6 7 8 9 10 11 const  OptimizeCssAssetsWebpackPlugin  = require ("optimize-css-assets-webpack-plugin" );const  TerserWebpackPlugin  = require ("terser-webpack-plugin" );module .exports  = {  optimization : {     minimizer : [       new  TerserWebpackPlugin (),        new  OptimizeCssAssetsWebpackPlugin (),      ],   }, }; 
 
Webpack 输出文件名 Hash 
在部署时服务器开启静态文件缓存,如果缓存时间过长,程序发生更改,得不到更新 
生产环境下,文件名使用 Hash,文件名发生变化 对客户端而言全新的文件名就是全新的请求,就不会有缓存问题 
[hash]【占位符】:整个项目级别的,项目中有任何地方改动,这次打包的 hash 值都会发生变化 
[chunkhash]【占位符】:chuankhash 级别,打包过程中只要是同一路的打包 chunkhash 都是相同的 
[contenthask]【占位符】:文件级别 hash,根据文件输出内容生成 hash 值(不同的文件就有不同的 hash 值) 
指定 hash 值长度[chunkhash:8]【占位符】 
 
1 2 3 4 5 module .exports  = {  output : {     filename : "[name]-[contenthash:8].bundle.js" ,   }, }; 
 
Polyfill 可翻译为垫片,
引入方法 
入口文件引入 
 
1 2 3 4 5 #  npm安装 pnpm add core-js regenerator-runtime #  在入口文件顶部导入 import 'core-js/stable' import 'regenerator-runtime/runtime' 
 
webpack 引入 
 
1 2 3 moduls.exports  = {   entry : ["core-js/stable" , "./src/index.js" ], }; 
 
预设配置 target 1 2 3 4 5 6 7 8 9 10 11 12 13 module .exports  = {  presets : [     [       "@babel/preset-env" ,       {         target : {           chrome : 30 ,         },       },     ],   ], }; 
 
配合browserslistrc 这个文件是目标环境配置表,也就是说通过配置,可以让我们的代码运行在哪些指定的浏览器或 node 环境,比如下边这种配置的意思就是,市场份额大于 1%的浏览器,并且不考虑 ie10 以下版本的浏览器,并且不是已’死亡’(指 24 个月没有官方的更新)的浏览器.
1 2 3 4 > 1 % not ie <= 10  not dead 
 
上面预设配置第一个参数 targets 里边就可以填写.browserslistrc 中的内容,效果是一样的,
useBuiltIns useBuiltIns有三个值.默认是false.其他是entry和usage.entry: 需要在入口或者 webpack 配置文件中引入core-js. Babel 会根据我们.browserslistrc文件中的环境配置(这里是 chrome 60 版本),来补齐 chrome 60 版本所有不支持的新增 ES6API,引入 chrome60 版本所有的 polyfill,不管你的入口文件中有没有用到相应的 API。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 npm install core-js -S npm install regenerator-runtime -S (根据@babel/core版本判断是否需要安装) module .exports  = {  presets : [["@babel/preset-env" , {     useBuiltIns : 'entry' ,     corejs : 3    }]],   plugins : [] } import  'core-js/stable' let  promise = Promise .resolve ('success' )chrome 60  
 
usage: 就把上面 entry 改了就行,作用就是类似于按需加载,只把用的 Promsie 进行 polyfill 补齐.
corejs 就是 corejs 的版本,推荐 3.
modules 可以配置成”amd” | “umd” | “systemjs” | “commonjs” | “cjs” | “auto” | false,默认为 auto,这个参数的含义是,是否将 ES6 模块化语法转译成其他的语法,设置为 false 的话,转译的结果就还是 ES6 模块化语法,就是 import,export default 这种,不会使用 require(CommonJs 语法)这种进行导入导出,如果使用 webpack 或 rollup 等打包工具,推荐将 modules 设置为 false,这样的话就有利于打包工具进行静态分析,从而做 tree shaking 等优化操作
范例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 module .exports  = {  presets : [     [       "@babel/preset-env" ,       {                                    useBuiltIns : "usage" ,         corejs : {                      version : "^3.25.2" ,                      proposals : true ,         },                  modules : false ,       },     ],   ],   plugins : ["@babel/plugin-transform-runtime" ], }; 
 
Postcss 用来处理 css 样式兼容性的工具,用于适配不同的浏览器.
1 pnpm add postcss-cli postcss-loader autoprefixer 
 
autoprefixer 自动适配浏览器,在browserslistrc文件中写上要适配的浏览器种类
1 2 3 4 5 6 7 >0.01 % last 2  versions not dead 
 
可以将配置写在单独的postcss.config.js中,或者 webpack 配置中
1 2 3 4 module .exports  = {  plugins : [require ("autoprefixer" )], }; 
 
postcss-preset-env 在一些陈旧的项目中可能会看到 autoprefixer 这个插件,现在我们可以直接使用 postcss-preset-env 这个插件,自动添加前缀的功能已经包含在了其中
在项目根目录下新增 postcss.config.js 配置文件:
1 2 3 module .exports  = {  plugins : ["postcss-preset-env" ], }; 
 
postcss-loader 在 webpack 中的配置
1 2 3 4 5 6 7 8 9 10 11 moudule.exports  = {      modules : {     rules : [       {         test : /.css$/ ,         use : ["style-loader" , "css-loader" , "postcss-loader" ],       },     ],   }, }; 
 
静态资源 在 webpack5 之前,加载图片、字体等资源需要我们使用 url-loader、 file-loader 等来处理 从 webpack5 开始,我们可以使用内置的资源模块类型 Asset Modules type,来替代这些 loader 的工作 资源模块类型 Asset Modules type 分为四种:
asset/resource 发送一个单独的文件并导出 URL,之前通过使用 file-loader 实现 
asset/inline 导出一个资源的 data URI,之前通过使用 url-loader 实现 
asset/source 导出资源的源代码,之前通过使用 raw-loader 实现 
asset 在导出一个 data URI 和发送一个单独的文件之间自动选择,之前通过使用 url-loader 实现,并且可以配置资源体积限制 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 module .epxorts  = {  module : {     rules : [       {         test : /\.(png|svg|jpe?g|gif)$/ ,         type : "asset" ,         generator : {           filename : "images/[name]-[hash][ext]" ,         },       },     ],   }, }; 
 
文件缓存 webpack5 之前我们常用 cache-loader 、hard-source-webpack-plugin 做文件缓存,加速二次构建, webpack5 现在内置了这种能力,默认会把编译的结果缓存到内存中,通过配置还可以缓存到文件系统中 修改 webpack.base.js:
1 2 3 4 5 6 7 module .exports  = {     cache : {     type : "filesystem" ,   },    }; 
 
多个 webpack 配置文件 可以根据开发环境配置多个 webpack 配置文件,将共配置抽离出来。 一般配置如下: 基础:webpack.base.config.js 开发:webpack.dev.config.js 生产:webpack.prod.config.js
webpack-merge 使用该插件进行合并操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const  { merge } = require ("webpack-merge" );const  baseConfig = require ("./webapck.base" );const  devConfig = {  mode : "development" ,   devtool : "cheap-module-eval-source-map" ,   devServer : {},    }; module .exports  = merge (baseConfig, devConfig);