Skip to content

打包

webpack中的loader和plugin原理

Webpack插件是用来扩展Webpack功能的JavaScript对象,它们可以在Webpack编译过程中执行自定义的任务,例如:优化资源、压缩代码、代码分割等。Webpack插件机制的核心是一个钩子系统(Hook System),Webpack在不同的编译阶段提供不同的钩子,插件可以监听这些钩子,根据自己的需求来执行相应的操作。

js
class MyPlugin {
  apply(compiler) {
    compiler.hooks.done.tap('MyPlugin', () => {
      console.log('Webpack compilation finished.');
    });
  }
}
 
module.exports = {
  //...
  plugins: [
    new MyPlugin()
  ]
};

Webpack Loader是用来处理非JavaScript文件的Webpack插件,它们可以将非JavaScript文件转换为JavaScript模块,以便在Webpack中引用和使用。Webpack Loader机制的核心是一个转换函数,这个函数接收一个源文件作为输入,然后输出一个JavaScript模块代码字符串。

如 将Less或Sass文件转换为CSS文件、将TypeScript文件转换为JavaScript文件等。

js
module.exports = function(source) {
  const json = JSON.parse(source);
  const moduleCode = `module.exports = ${JSON.stringify(json)};`;
  return moduleCode;
};

webpack & vite 热更新HMR原理

主要是通过WebSocket创建浏览器和服务器的通信监听文件的改变,当文件被修改时,服务端发送消息通知客户端修改相应的代码,客户端对应不同的文件进行不同操作的更新。

Webpack:重新编译打包,请求打包后的文件,客户端进行重新加载。 Vite:请求变更后的模块,浏览器直接重新加载。

Vite

在Vite项目中的index.html中,我们可以看到如下代码,是用于请求我们的main.js:

html
<script type="module" src="/src/main.js"></script>

此时会向当前服务器发送一个GET请求用于请求main.ts

ts
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')

请求到了main.ts文件,检测到内部含有import引入的包,又会import 引用发起HTTP请求获取模块的内容文件,如App.vue或者其他的vue文件。

Vite其核心原理是利用浏览器现在已经支持ES6的import,碰见import就会发送一个HTTP请求去加载文件,Vite启动一个 koa 服务器拦截这些请求,并在后端进行相应的处理将项目中使用的文件通过简单的分解与整合(比如将 Vue 文件拆分成 template、style、script 三个部分),然后再以ESM格式返回返回给浏览器。Vite整个过程中没有对文件进行打包编译,做到了真正的按需加载,所以其运行速度比原始的webpack开发时还需进行编译编译打包的速度相比快出许多!

todo:一文带你了解Vite原理

模块化规范

  • CommonJS:一种使用在 Node.js 中的模块化规范,该规范使用 require 函数加载模块,使用 module.exports 或 exports 对象导出模块。

  • AMD(Asynchronous Module Definition):一种异步加载模块的规范,该规范使用 define 函数定义模块,使用 require 函数异步加载模块。

  • UMD(Universal Module Definition):一种通用的模块化规范,既支持 CommonJS 的同步加载模块,也支持 AMD 的异步加载模块。

  • ES6 Module:一种使用在现代浏览器和 Node.js 中的模块化规范,该规范使用 import 语句加载模块,使用 export 关键字导出模块。

ES6 Module 和 CommonJS 模块的区别:

  • 加载方式:ES6 Module 是一种静态加载模块的规范,它使用 import 加载模块,导出使用 export ;CommonJS 是一种同步加载模块的规范,它使用 require 函数加载模块,导出使用 module.exports 或 exports。

  • 浏览器支持:ES6 Module 是浏览器原生支持的模块化规范,可以直接在浏览器中使用;CommonJS 是 Node.js 中使用的模块化规范,在浏览器中需要使用工具将其转换为浏览器可用的代码。

  • 运行时执行:ES6 Module 中,模块的导入和导出是在编译时执行的,因此可以在编译时进行静态分析和优化;Webpack中的 tree shaking 实际上就是依赖ES6模块化,在编译时处理;CommonJS 中,模块的导入和导出是在运行时执行的,因此无法在编译时进行静态分析。

  • 输出内容:CommonJS模块输出的是一个值的拷贝,ES6 模块输出的是值的引用,类似 const;

  • this指向:在CommonJS顶层,this指向当前模块;而在ES6模块中,this指向undefined;

ES6 Module 和 CommonJS 模块的共同点:

  • 1.作用域:ES6 Module 中,每个模块都是单独的作用域,模块内定义的变量只能在模块内部访问,不会污染全局作用域;CommonJS 中,每个模块都有自己的作用域,模块内定义的变量无法在其他模块中访问。

  • 2.修改对象内部属性:CommonJS 和 ES6 Module 都可以对引入的对象进行赋值,即对对象内部属性的值进行改变。

声明一个npm包是commonjs还是esm

在 package.json 的 "main" 字段中指定 commonJS 版本的入口文件,在 "module" 字段中指定 esModule 版本的入口文件。(types 字段指定类型导出)

json
{
  "name": "my-package",
  "main": "dist/cjs/index.js",
  "module": "dist/esm/index.js"
}

后来又加了 exports 字段,设置了exports之后,没有在exports字段导出的文件是不能被下游用户导入的;

最佳实践是把main,module,exports都写上,这样太新的打包器在不支持exports的时候也会fallback到module或者main字段。

json
{
  "main": "dist/index.cjs",
  "module": "dist/index.mjs",
  "types": "./dist/types/src/index.d.ts",
  "exports": {
    ".": {
      "types": "./dist/types/src/index.d.ts",
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs"
    },
    "./package.json": "./package.json"
  }
}

command 命令如果实现

如 项目中执行 jest ,为什么不会报错;

因为当我们在项目中安装 Jest 时,它会自动将 Jest 的可执行文件添加到项目的 node_modules/.bin 目录下。而 node_modules/.bin 目录会被自动添加到系统的 PATH 环境变量中.

shell
npx create-react-app my-app

npx 是 npm 5.2+ 版本中新增的一个命令,用于执行本地安装的可执行文件,或者远程仓库中的可执行文件。当我们在命令行中使用 npx 命令执行某个命令时,它会自动查找当前项目中是否安装了该命令,如果没有安装,则会在临时目录中下载并执行该命令,执行完毕后自动清理临时文件。

上面的命令中,我们使用 npx 命令执行本地安装的 create-react-app 命令。由于 create-react-app 命令不是全局安装的可执行文件,因此需要使用 npx 命令来执行它。

在实践中,我们可以在需要执行某个命令时先尝试使用命令名称来执行,如果提示找不到该命令,则可以尝试使用 npx 命令来执行。

npm包自定义cli

js
#!/usr/bin/env node

const i18nGenerate = require("../lib/index").default
const args = require('minimist')(process.argv.slice(2))
new i18nGenerate(args.mode, args.project).generate()
json
// package.json 中声明
{
  "bin": {
    "soul-i18n": "bin/index.js"
  }
}
sh
node my-cli.js --name John --age 30
js
process.argv: [
  '/usr/local/bin/node',
  '/path/to/my-cli.js',
  '--name',
  'John',
  '--age',
  '30'
]