工程化相关(一)(webpack 核心及原理)

593 阅读18分钟

📃 前言:demo-webpack5代码地址:

🔗 gitee:code for demo-webpack@5

🔗 github:demo-webpack@5

demo-webpack.png

一、npx作用

在工程中,用到一些命令工具,若全局安装,无法有效控制命令工具的版本,因此常针对不同工程,采用局部安装命令工具的方式,此时直接使用命令会报错,因为命令工具安装在工程目录 node_modules/.bin/xx 中,npx 命令xxx 的作用就是直接在该目录下找命令工具,而非全局找

  • 在工程 node_modules/.bin/xx 中使用命令行工具

package.json 中配置脚本可以省略 npx

{
"scripts":"webpack" // 等价于 npx webpack
}
  • 临时安装命令工具(非工程目录)并执行

当执行某个命令时,如果无法从本地工程中找到对应命令,则会把命令对应的包下载到一个临时目录,下载完成后执行,临时目录中的命令会在适当的时候删除

npx prettyjson 1.json

上述命令会将包 prettyjson 安装到临时目录,并执行命令,如果包名和命令名称不一致,可以手动指定包名,如包名 @vue/cli,命令名称 vue ,可使用命令

npx -p @vue/cli vue create my-app

二、Eslint 代码规范

1、安装
npm i -D eslint
2、校验(使用 eslint 命名行工具)
# 单个文件校验
npx eslint 文件名
​
# 校验全部文件
npx eslint src/**
3、配置文件
  • .eslintrc JSON文件
  • .eslintrc.cjs 或 .eslintrc.js JS文件
  • .eslintrc.yml yaml 文件
// .eslintrc.jsmodule.exports = {
  rules:{
      // 规则级别 'off'或0 - 关闭规则;'warn'或1 - 验证不通过警告;'error'或 2 - 验证不通过退出程序
      // 规则名:规则级别
      eqeqeq:2
  }  
}

三、typescript 配置文件

  • 补充

1、约束类的类型可以使用 new () => object

// 约束一个类
const target:new (...args:any[]) => object
  • 配置文件说明

    -- tsconfig.json

{
  // "extends":[], // 指定继承配置
  // "files": [], // 指定编译的特定文件
  // 编译选项
  "compilerOptions": {
    // 1、【语言与环境 Language and Environment】
    "target": "ES2016", // 编译目标代码的版本标准,默认值 es3
    "lib": ["esnext", "dom"], // ts需要引入的库,即类型申明文件,在TypeScript源码的lib目录下查看具体内容
    "jsx": "preserve", // 指定自定义的 JSX 工厂函数库,用于处理 JSX 代码的转换:react(react@18以下);react-jsx(react@18)
    /**
     * jsx 不同取值用法
     * preserve:适用于需要将JSX处理委托给其他工具(Babel)的场景
     * react:转换成.js文件,将 JSX 语法转换成React.createElement,源代码必须导入react,适用于 react16及更早版本
     * react-jsx:适用于 react17及以上版本,JSX 语法不会转换成React.createElement,源代码不用导入react,而是自动从react包中导入特殊函数并调用
     */
    "jsxImportSource": "vue", // 保留 JSX 代码,不进行转换。这通常用于与 Babel 等工具链配合使用,一般vue中使用
    // "useDefineForClassFields": false // 控制类字段的编译方式,指定是否使用 define 语法,确保类字段的行为符合最新的 ECMAScript 标准,默认值:target>=2022 true,其他默认false
    // "noLib":true, // 禁止包含任何库文件,包括默认的 lib.d.ts,默认值为 false
    // "experimentalDecorators":true, // 启用对装饰器的实验性支持,默认 false
    // "emitDecoratorMetadata":true, // 启用对类型元数据reflect-metadata的支持,默认 false

    // 2、【类型检查 Type Checking】
    "strict": true, // 启用所有严格类型检查
    // -- start
    // "alwaysStrict" :false,// 启用严格模式,为每个文件添加 "use strict"
    // "noImplicitAny": false, // 禁止隐式的 any 类型
    "noImplicitThis": false, // 确保 this 的使用有明确的类型声明
    "strictBindCallApply": true, // 确保 bind、call 和 apply 的参数符合函数签名的要求
    "strictNullChecks": true, // 确保处理 null 和 undefined,减少潜在的运行时错误
    "strictPropertyInitialization": true, // 防止类属性在实例化时未被正确初始化
    // "strictFunctionTypes": false,// 确保函数参数和返回值类型严格匹配
    // "useUnknownInCatchVariables":false, // 默认 catch 子句变量为unknown,而不是any。
    // --end 上面的8个配置,会跟随 strict:true 自动开启

    "noUnusedLocals": true, // 禁止存在未使用的局部变量
    "noUnusedParameters": true, // 禁止存在未使用的函数参数
    // "noFallthroughCasesInSwitch": false, // 防止在 switch 语句中遗漏 break 语句,从而减少潜在的逻辑错误

    // 3、【模块相关 Module】
    "module": "ESNext", // 编译结果的模块化标准,如果target设置为ES3或者ES5,默认为CommonJS
    "moduleResolution": "Bundler", // 模块解析的模式,Node - node查找策略;bundler - ts@4.7+推荐使用,与打包工具(webpack、esbuild、rollup)配合
    // 路径映射,一般用来和打包工具一起定义路径别名
    //  paths定义的路径映射并不会影响到编译之后的js文件,这个定义仅仅会阻止TypeScript报错,因为tsc编译器并不会处理模块说明符(也就是from "xxxx")里面的内容
    "paths": {
      "@/*": ["./src/*"]
    },
    "resolveJsonModule": true, // 允许导入 JSON 文件作为模块
    // 需要设置noEmit或者emitDeclarationOnly为true
    "allowImportingTsExtensions": true, // 允许在 import 语句中使用 .ts 和 .tsx 扩展名
    // "baseUrl": "./", // 设置解析非相对路径模块的基础地址,默认是当前目录,这里./的路径是相对于tsconfig文件的地址的

    // 4、【互操作约束 Interop Constraints】
    "esModuleInterop": true, // 允许与CommonJS模块和Es module互操作,如果module是node16或nodenext,默认开启
    "isolatedModules": true, // 确保 TypeScript 编译器和 Babel(或其他类似工具)之间的兼容性,特别是在使用单独编译模块时
    // 当esModuleInterop:true时默认开启为true
    "allowSyntheticDefaultImports": true, // 允许commonjs没有默认导出的代码使用ESM的默认导出行为
    // "forceConsistentCasingInFileNames":true, // 引入模块文件时的文件名必须与文件系统中的文件名大小写一致

    // 5、【生成文件,影响编译结果 Emit】
    "noEmit": true, // 用于仅进行类型检查而不生成输出文件,比如在测试代码时
    "removeComments": true, // 编译后的 .js 文件中删除注释
    // "declaration":true, // 生成类型声明文件.d.ts
    // "emitDeclarationOnly":false, 只生成类型声明文件.d.ts,不生成.js文件
    // "declarationMap":true,// 为.d.ts文件创建源映射
    // "outDir":null, // 指定输出目录,未指定默认为编译文件的同目录

    // 6、【js 支持 Support js】
    "allowJs": true, // 允许使用js文件
    // "checkJs":true, // js 文件也进行类型检查

    // 7、【其它 Other】
    "skipLibCheck": true // 跳过库文件的类型检查
  },
  // 指定编译的文件或目录
  "include": ["env.d.ts", "src/**/*", "src/**/*.vue", "types/auto-imports.d.ts"],
  // 指定编译器需要排除的文件或文件夹,默认值 node_modules,bower_components,jspm_packages,outDir配置的对应目录
  "exclude": ["src/**/__tests__/*"],

  // 更精细的控制,指定项目间的依赖关系,明确主项目依赖于哪些子项目或模块,帮助 TypeScript 确定编译顺序,确保依赖项先编译
  // 实现增量编译,提升效率,通常用于前后端部署在一起,且后端基于 node 开发时配置
  // "references": [
  //   {
  //     "path": "./tsconfig.node.json" // node 环境
  //   },
  //   {
  //     "path": "./tsconfig.app.json" // 浏览器环境
  //   }
  // ],

  // ts-node 相关配置
  "ts-node": {
    "esm": true
  }
}

四、Webpack 构建工具

📚【核心功能】
1、安装和使用
  • 需要下载 webpack 和 命令行工具 webpack-cli
npm i -D webpack webpack-cli

webpack :核心包,包含前端工程构建过程所有 api

webpack-cli:命令行工具,可以通过命令调用核心包的 api,完成工程的构建

  • 使用命令 webpack 默认从 "./src/index.js" 该目录为入口文件,分析工程依赖关系,最终打包到目录 "./dist/main.js"

可以使用 --mode 指定打包运行的环境,默认生产环境(production

生产环境构建后的代码会压缩,开发环境不会

# 构建工程(默认生产环境),等价于 webpack --mode=production
webpack
# 指定开发环境构建
webpack --mode=development
2、配置文件
  • webpack 默认读取 webpack.config.js 作为配置文件,也可以使用命令指定配置文件
# 指定配置文件
webpack --config 文件路径

配置文件内容使用 CommonJS 规范导出一个对象

问: webpack 支持多模块化规范,但配置文件导出的配置对象,为何必须使用 CommonJS 规范?

💡 因为工程中的整个模块文件在编译过程中只是读取,识别模块之间的依赖关系,最终生成打包文件,这些文件在编译过程中是不参与运行的,而配置文件 webpack.config.js 是要参与运行的,因为整个编译过程是在 node 环境中,而后缀名为 js 时,默认是按照 CommonJS 解析的,并且 webpack 内部导入 webpack.config.js 是用的CommonJS,因此只能使用 CommonJS 规范

node 环境中

1)默认情况下, .js 文件 — CommonJS 规范, .mjs 文件 — EsModule 规范

2)工程在 package.json 中设置了 type:"module" ,则 .js 文件 — Esmodule 规范, .cjs 文件 — CommonJs 规范

// 配置文件 webpack.config.jsconst path = require('path');
​
module.exports = {
    // 打包运行环境 development - 开发,production - 生产
    mode:"development",
    // 入口文件,通常是字符串(单入口),多入-单出时(字符串数组),多入-多出时(对象-{打包文件名:"路径"})
    // 入口文件不配置,默认是 "./src/index.js"
    entry:"./main.js",
    // 出口文件
    output:{
       path:path.resolve(__dirname,'./dist'),// 打包的路径,默认是 ./dist
       filename:"main.js", // 打包后的文件名称
    }
}
3、devtool 配置
  • source map 源码地图

在开发环境运用于调试用,运行打包后文件时,若报错,能定位打包前源码位置

image-20240808152643462.png

  • webpack 使用 devtool 配置来优化调试体验,有很多方式,使用的时候查看官网即可
4、编译过程
  • 初始化

webpack 将 【cli参数】、【配置文件】、【默认配置】进行融合,形成最终配置文件,对配置的处理过程依托第三方库 yargs(分析命令行指令)

是编译过程的准备工作,即生成配置文件

  • 编译

1)创建 chunk (块),通过入口文件找到所有依赖的统称,通常为一个(单入),也可以多个

image-20240808155213734.png

每个 chunk 至少2个属性:

  • name:默认为 main
  • id:唯一编号,开发环境和name相同,生产环境是一个数字,从0开始

2)构建所有模块依赖,生成模块列表(模块id和转换后的代码)

image-20240808155539250.png


3)产生 chunk assets,根据配置,生成最终生成文件的资源列表(文件名-文件内容)

image-20240808161034989.png

👉 chunk hash 是根据所有chunk assets的内容生成的一个hash字符串(hash是一种算法,内容不变,得到的hash值不变)

image-20240808161504682.png


4)将多个chunkhash assets 合并到一起,产生一个总的 hash

image-20240808161823837.png

  • 输出

webpack 利用 node 的 fs 模块(文件处理模块),根据编译产生的总的 assets 生成不同的文件

  • 总过程及术语 image-20240808162343814.png

术语:

  1. module:模块,分割的代码单元,webpack 中的模块可以是任何内容的文件,不仅限于JS
  2. chunk:webpack 内部构建模块的块,一个 chunk 中包含多个模块,这些模块是从入口模块通过依赖分析得来的
  3. bundle:chunk 构建好模块后会生成chunk的资源清单,清单中的每一项就是一个bundle,可以认为 bundle 就是最终生成的文件
  4. hash:最终的资源清单(多个 chunk 生成的资源清单汇总)所有内容联合生成的 hash
  5. chunkhash:chunk 生成的资源清单内容联合生成的 hash
  6. chunkname:chunk 的名称,如果没有配置则使用 main
  7. id:通常指 chunk 的唯一编号,如果在开发环境下构建,和 chunkname 相同;如果是生产环境下构建,则使用一个从0开始的数字进行编号

image-20240808165950571.png

image-20240808165907470.png

5、入口和出口
  • node 中路径说明

📍 ./

1)在模块化代码中,比如 ruquire( "./xx" ),表示当前 js 文件所在目录

2)在路径处理中,”./” 表示 node 运行目录

📍 __dirname 所有情况下,都表示当前运行 js 文件所在目录,是个绝对路径

  • 多入多出中,出口文件的配置规则

    💡 说明:output 中指定 filename 时,想将打包的文件放入子文件夹中,可以配置成 filename : 'scripts/[name].[hash:5].js',这样就放入子文件夹 scripts 中了

1)name - chunkname

2)hash - 总的资源 hash,通常用于解决缓存问题,内容不变,hash 不变

3)chunkhash - 使用 chunkhash

const { resolve } = require('path');
    ​
module.exports = {
  // 入口
  entry:{
      // 属性为 chunkname,值为入口模块路径
      main:'./src/index.js', 
      a:'./src/a.js',
      b:['./src/b/index.js','./src/b/other.js'] // 针对b有多个入口文件
   },
  // 出口
  output:{
      // 必须为绝对路径
      path:resolve(__dirname,'dist/app'),
      // 出口文件名称,这里的 name 是占位符,对应替换为入口定义的 chunkname
      // filename:'[name].bundle.js'// hash:指定位数
      filename:'[name].[hash:5].js'
  }
}
6、扩展(loader、plugin)

webpack 只是分析模块依赖关系,生成模块列表,并根据配置文件,生成资源文件列表,最后将多个资源文件列表合并生成打包文件,而更多的功能,需要扩展 webpack loaderswebpack plugins

  • loader

    • loader 本质是一个函数,它的作用是将某个源码字符串(webpack 读取的源码当作字符串解析)转换成另一个源码字符串返回

    • loader 函数将在模块解析的过程中被调用,以得到最终的源码

    image-20240808203300009.png

    image-20240808203153841.png

    • loader 配置

模块规则配置 module 中,运用的匹配规则 rules,多个规则对象是从上往下匹配,匹配到后统一加入一个数组(记作tempRules)中,当运行时相当于 tempRules.pop()

🎉 下列匹配到后,执行顺序是 loader4 > loader3 > loader2 > loader1

// 匹配到的规则后将 loader 放入数组 ['loader1','loader2','loader3','loader4']
module.exports = {
   module:{
       rules:[
           { test:/index.js$/,use:[ 'loader1','loader2' ] },
           { test:/.js$/,use:[ 'loader3','loader4' ] },
        ]
    }
}

🍍 简要配置(对应loader函数参数用默认值)

 modele.exports = {
   // 模块处理配置
   module:{
      // 模块匹配规则,可以有多个
      rules:[
          {
             test:/.js$/, // 模块匹配的正则
             // 匹配到的模块应用的规则模块(使用哪些加载器)
             use: ["模块路径1", "模块路径2"] // loader模块的路径,该字符串会被放置到require中
           }
      ]
   }
 }

🍍🍍 完整配置(向对应loader函数传递额外参数)

可以使用第三方库 loader-utils 获取 options 中传递的参数,或生成文件路径等新版 loader-utils 少了很多方法,如 getOptions ,因为 webpack@5loader 函数中,可以通过 this.query 获取配置 options

 # 开发环境下载库
 npm i -D loader-utils
modele.exports = {
   // 模块处理配置
   module:{
     // 模块匹配规则,可以有多个
     rules:[
       {
          test:/.js$/, // 模块匹配的正则
          // 匹配到的模块应用的规则模块(使用哪些加载器)
          use: [
             {
               loader:'模块路径1',// loader模块的路径,该字符串会被放置到require中
               options:{ ... } // 传递的额外参数
             }
          ]
        }
     ]
   }
}

🔧 自定义 loader

定义(CommonJs 定义函数并返回字符串源码)

因为 webpack 最终源码编译结果( js 文件)是一个立即执行函数,并在内部通过 eval( 源码字符串 ) 来执行代码,因此自定义 loader 返回的字符串源码中编写需要执行的逻辑

image-20240812104848270.png

如果给自定义 loader 函数绑定静态属性 raw,那获取的数据格式不会转为字符串,而是原始格式

// 定义loader方法
function loaderFn(){
   // ...
 }
 ​
// 绑定 raw 并导出方法
loaderFn.raw = true;
module.exports = loaderFn

--- img-loader.js

const loaderUtils = require('loader-utils');
console.log(loaderUtils);
            ​
/**
 * 图片loader
 * @param {Buffer} buffer 图片二进制数据
 */
function imgLoader(buffer) {
   // 文件大小 KB
   const size = buffer.byteLength / 1024;
   console.log(`文件大小:${Math.floor(size)}KB`);
   console.log(this.query);

   // 通过 loader 上下文中的query,可以获取 loader 配置中的 options
   const { limit = 1024 , name = '[contenthash:5].[ext]' } = this.query;
   let content = '';
   const { fileName, ext } = getFilePath.call(this, buffer,name);
   if (size < limit) {
     content = getBase64(buffer, ext);
   } else {
     content = fileName;
     // 打包文件到最终目录
     this.emitFile(fileName, buffer);
   }
  return `module.exports=`${content}``;
}
// 让loader 获取到的数据格式为原始格式(这里是 buffer)
imgLoader.raw = true;
​
/**
 * 获取文件名称
 * @param {Buffer} buffer 图片二进制数据
 * @param {string} name 图片打包后文件名称格式
 */
function getFilePath(buffer,name) {
  const fileName = loaderUtils.interpolateName(this, name, {
    content: buffer
  });
  return { fileName, ext: fileName.replace(/.*./g, '') };
}
​
/**
 * 获取图片base64格式
 * @param {Buffer} buffer 图片二进制数据
 * @param {string} ext 图片后缀
 */
function getBase64(buffer, ext) {
  const fileExt = ext === 'jpg' ? 'jpeg' : ext;
  return `data:img/${fileExt};base64,${buffer.toString('base64')}`;
}
​
module.exports = imgLoader;

✨✨🧑🏻使用

image-20240812162914422.png

  • plugin

    • plugin 的作用是在编译过程中,嵌入一些额外的功能

    • plugin 的本质是一个带有 apply 方法的对象,通常写成一个构造函数的模式

const plugin = {
    apply:function(){
        // ...
    }
}
​
// 通常为构造函数
module.exports = class MyPlugin{
    apply(compiler){
        // ...
    }
}
// 插件实例
const myPlugin = new MyPlugin()

👉 使用 - 配置 plugins

const MyPlugin = require('./src/plugins/myPlugin.js')
​
module.exports = {
    plugins:[
        new MyPlugin()
    ]
}

🔧 自定义插件

apply 方法会在初始化阶段,创建好 compiler 对象后,将 compiler 对象作为参数传入并调用(apply 方法) ,整个构建过程中只有一个 compiler 对象,后续完成打包工作的是 compiler 对象内部创建的 compilation

image-20240812170744427.png

compiler 对象提供了大量的钩子函数(hooks,可以理解为事件),plugin 的开发者可以注册这些钩子函数,参与 webpack 编译和生成。

你可以在apply方法中使用下面的代码注册钩子函数:

class MyPlugin{
   apply(compiler){
       compiler.hooks.事件名称.事件类型(name, function(compilation){
          // 事件处理函数

         // 可以通过 compilation.assets 获取到打包后生成的文件信息,是个对象
         // 形如: {'文件名称':{ source(){return '源码字符串或文件二进制'},size(){return '文件字节数'} }}
       })
   }
}

【事件类型】

  • tap 注册一个同步的钩子函数,函数运行完毕则表示事件处理结束
  • tapAsync 注册一个基于回调的异步的钩子函数,函数通过调用一个回调表示事件处理结束
  • tapPromise 注册一个基于Promise的异步的钩子函数,函数通过返回的 Promise进入已决状态表示事件处理结束
module.exports = class FileListPlugin {
  constructor(fileName) {
    this._fileName = fileName;
  }
​
  // 插件方法
  apply(compiler) {
    console.log('插件运行');
    // 注册生成打包文件前的事件
    compiler.hooks.emit.tap('fileListPlugin', compilation => {
      const fileContent = [];
      for (const fileName in compilation.assets) {
        // js文件源码字符串或文件二进制数据
        const source = compilation.assets[fileName].source();
        // 文件字节数
        const size = compilation.assets[fileName].size();
        fileContent.push(`--【${fileName}】\n文件大小:${size / 1024}KB\n`);
      }
      console.log(`生成文件${this._fileName}`);
      const _size = fileContent.join('\n\n');
      compilation.assets[this._fileName] = {
        source() {
          return _size;
        },
        size() {
          return _size.length;
        }
      };
    });
  }
};
​
7、区分环境
  • 配置文件

    通常是导出一个配置对象,也可以是个方法,方法的返回值是一个配置对象,这个函数会在开始构建的时候调用,传入参数 env,这样可以根据判断等实现不同环境不同的配置

 module.exports = function( env ){
        return {
            // 配置内容
        }
}
  • 上述 env 的值可通过终端命令控制
# webpack@4.x 的写法
npx webpack --env abc # env: "abc"
npx webpack --env.abc # env: {abc:true}
npx webpack --env.abc=1  # env: {abc:1}
npx webpack --env.abc=1 --env.bcd=2 # env: {abc:1, bcd:2}

# webpack@5.x 的写法,没有符号 .
npx webpack --env abc # env:{abc:true}
npx webpack --env abc=1 # env: {abc:1}
  • 项目中针对不同环境若用到不同的环境变量,如 TITLE(标题差异)、GET_WAY(请求的网关不同)等,可以用第三方库 dotenv 读取 .env.env 开头的文件,并在 webpack.config.js 配置文件中注入,则可以通过 process.env.xx 访问

    1、package.json 文件中,通过脚本设置环境变量区分环境

    2、dotenv 默认读取 .env 文件,可指定其它 .env 开头的文件

    3、在 webpack.config.js 中注入环境变量

"scripts": {
    "start": "webpack-dev-server --mode=development --node-env development",
    "build": "webpack --mode=development --node-env production",
    "build:prod": "webpack --mode=production --node-env production"
  },
# 项目标题
TITLE = 开发环境-myapp

# 其它
const { resolve } = require('path');
const dotenv = require('dotenv');

// 当前环境变量
const NODE_ENV = process.env.NODE_ENV;
// 读取环境变量并注入,默认会注入 .env 文件(全局环境变量),多文件的同名环境变量,后者不会覆盖前者,一旦确定不再更改
dotenv.config({ path: resolve(__dirname, `.env.${NODE_ENV}`) });
8、其他细枝末节的配置
  • 1)context

指定入口文件的绝对路径,通常入口文件配置中路径 './' 指当前运行终端的路径(即当前执行路径),配置了 context 会直接影响【入口 entry】和 loader 中的相对路径

entry:'./main.js',
// 指定入口绝对路径
context: path.resolve(__dirname, "app")
  • 2)output 选项中其他配置

library 会将打包结果中,自执行函数的执行结果暴露给设置的值,如下的 'abc',相当于:

 // 打包编译后的文件
 var abc = ( function( modules ){ return ... } )( )

libraryTarget 默认值为 'var' ,配合 library 使用,以控制如何暴露入口包的导出结果

  • var (默认值),暴露给一个普通变量
  • window,暴露给 window 对象的一个属性
  • global,暴露给 global 对象的一个属性
  • this ,暴露给 this 的一个属性
  • commonjs,暴露给 exports 的一个属性
// 出口配置
output:{
path:path.resolve( __dirname,'./dist' ),// 出口路径
filename:'[name].[hash:5].js',// 打包后文件名

library:'abc'
}
  • 3)target 设置打包结果运行的环境

target

  • web (默认值),web 环境中运行
  • nodenode 环境中运行
  • 4)module 选项中的其它配置

noparse ,正则匹配的模块不参与解析,通常用于大型单模块库(不依赖其他模块),以提升构建性能

module:{
    noparse:/jquery/
}
  • 5)resolve 用于控制模块解析过程
  • modules 指定模块查找目录,默认 [ 'node_modules' ] ,即在导入模块时,路径没有写 './' 或 '../' 的,如 require( 'jquery' )
  • extensions 译为扩展名,即指定解析过程中,自动补全的后缀名,webpack 会根据配置中 extensions ,自动补全后缀名,例如:require('./a') ,首先看有没有该文件,没有,webpack 会自动补全后缀 .js.json 看是否存在
  • alias 路径别名
resolve:{
modules:['node_modules'],// 指定模块查找目录
extensions:['.js','.json'],// 指定补全文件后缀
// 路径别名
alias:{
   '@':path.resolve(__dirname,'src'),
   '_':dirname
}
}
  • 6)externals (外观)作用是排除最终的 bundle 中配置的源码,如下排除 jquerylodash 的导入,自己开发中仍然可以写 'import $ from 'jquery' 并使用,不会报错,而 webpack 编译后的代码(立即执行函数)中没有 jquery 的逻辑代码,转而导出的只是设置的字符串,通常该配置适用于通过 CDN 的方式引入的包,以减小构建体积,而书写源码里正常写
externals:{
    jquery:'$',
    lodash:'_'
}
  • 7)publicPath,通常配置在 outPut 中,就是在编译过程中指定了一个路径字符串,在资源加载的代码中,会拼接在开头
output:{
    publicPath:'/'// 通常配置成根路径
}

🧩 【常用扩展】
1、打包前清除之前的文件
  • clean-webpack-plugin
    • webpack@5 内置了该插件,仅需通过配置 output.cleantrue 即可,若排除部分文件的清除行为,可通过 output.clean.keep 控制保留文件
const { CleanWebpackPlugin } = require( 'clean-webpack-plugin' )
 
module.exports = {
    output:{
       // clean:true // 清除之前的打包文件
    },
    plugins:[
        new CleanWebpackPlugin( )
    ]
}
2、自动生成页面
  • html-webpack-plugin
const HtmlWebpackPlugin = require( 'html-webpack-plugin' )
        
module.exports = {
    // 多入口
    entry:{
       list:'./src/list/list.js',
       home:'./src/home/home.js'
    },
    plugins:[
        new HtmlWebpackPlugin( {
            template:'./public/index.html',// 指定模板,
            filename:'index.html',// 指定打包后的文件名称
            chunks:['list'] // 指定引入的 chunk
        } )
    ]
}
3、复制静态资源
  • copy-webpack-plugin
const CopyWebpackPlugin = require( 'copy-webpack-plugin' )
        
module.exports = {
    plugins:[
        new CopyWebpackPlugin([
            // 每个对象就是一个复制规则
            {
                from:'./public',// 从哪个资源复制
                to:'./' // 复制到哪个目录,该路径相对于output配置的打包路径
            }
        ])
    ]
}
4、开发环境服务器
  • webpack-dev-serverwebpack 官方的模块,使用 express 开启一个开发服务器,并将模块打包放入内存中,当页面请求资源时,再给与响应打包资源
module.exports = {
    devServer: {
    port: 8080, // 监听端口
    open: true, // 开启后默认打开页面,
    openPage:'index.html',// 默认打开页面,默认值为 'index.html'
    // 代理服务器
    // proxy: {
      // 代理规则
      // '/api': {
        // target: 'https://github.com',
        // changeOrigin: true // 更改请求头中的 host 和 origin
      // }
   // }
   
     // 上述配置方式在 webpack@4.x 中没问题,在 webpack@5.x中报错,提示 proxy 应配置为数组
     proxy:[
       {
         context:['/api'],
         target: 'http://github.com'
       }
     ]
  }
}
5、webpack 内置插件

所有 webpack 内置插件,都作为其静态属性存在,创建一个插件使用:

const webpack = requier('webpack');
    
// 创建一个插件
new webpack.插件名( options )
  • DefinePlugin 常量定义插件
new webpack.DefinePlugin({
    // 值为字符串,在编译后常量会替换为字符串的值
    PI:`Math.PI`,// const PI = Math.PI
    VISION:`'1.0.0'`
    DOMAIN:JSON.Stringify('duyi.com')
})
  • ProvidePlugin 自动导入常用的包
new webpack.ProvidePlugin({
    $:'jquery',
    _:'lodash'
})

📃 写在最后:推荐我的日常 demo 地址,涉及 jstsvuereact,欢迎访问 🎉🎉 ⭐

🔗 getee:daily_demo

🔗 github:daily_demo

image.png