# modules 模块化
# 模块化的优点:
可维护性、可复用性。
# 模块化的发展
1、ES6 之前没有模块化时,浏览器环境需要使用 script 进行引入 js 文件
2、后来使用全局变量 + 命名空间(namespace)处理,就是使用 IIFE 自动执行函数创建一个函数作用域,赋值给一个全局变量。
这样做的缺点是:依赖于全局变量,污染全局变量,不安全。
依赖于约定命名空间来避免冲突,可靠性不高。
需要手动管理依赖并控制执行顺序,容易出错。
需要在最终上线前手动合并所有用到的模块。
3、node 端模块系统:Common.js,缺点是浏览器端无法使用
4、AMD (Asynchronous module definition) 异步加载模块定义,CMD (模块标准)
采用异步方式加载,需要全局环境定义 require 和 define,不需要引入其他的变量。
5、ES6 modules、引入和暴露方式更加多样,支持复杂的静态分析和静态、动态导入。
# Bundler 打包工具
# 诞生原因:
使用 import export 这种异步加载方式在大多数浏览器中无法使用。
# 常见工具
一、Webpack(单 JS 文件入口),Webpack 采用代码分割、异步加载等技术,可以将多个模块打包成一个或者多个 bundle,,构建的产物是一个函数。Webpack 主要用于构建复杂的前端项目,如 Web 应用、SPA 单页面应用等,支持模块化开发、代码分割、资源优化和静态文件处理等功能。。
二、rollup 用静态的方式分析代码,对未使用的代码更加彻底的进行树摇优化,输出的 bundle 更加的精简。
三、snowpack(使用于新版浏览器)
1、出现的原因(其他 Bundler 的问题):
当资源越来越多时,打包的速度会越来越慢,大中型项目中,启动时间可能达到好几分钟。(vite 的理念来源于 snowpack,充分使用了新版浏览器支持 es modules 的特性。)
2、优势:
snowpack 利用新版浏览器支持 es modules 的特性,开发模式不会被打包,每个文件编译一次,永久被缓存,当一个文件修改的时候就只需要重新 build 那一个文件。(适用于新版浏览器)
四、vite
vite 基于 snowpack 的理念(利用新版浏览器支持 es modules 的特性),基于 es-modules,采用 rollup 进行构建 ,开发模式不会打包,每个文件当使用时才会去加载,修改时只需要重新 build 那一个文件。
五、vue-cli
vue-cli 底层基于 webpack,webpack 通过导入导出的语句分析整个项目,将目标代码转化成兼容浏览器的 js 代码,只有打包后的几个文件,这个过程需要耗费一些时间。
# webpack 配置支持 ES Module
webpack 默认只支持 CommonJS 规范,配置支持 ES Module 规范,需要进行如下配置:
一:将 ES Module 语法的 js 文件通过 webpack 进行打包即可。
二:webpack 默认 target 为 web,在使用 node 内置库时会报错,可以修改 target 属性。
const path = require('path'); | |
module.exports = { | |
entry: './bin/core.js', | |
output: { | |
path:path.join(__dirname,'/dist'), | |
filename: 'core.js', | |
}, | |
mode: 'production', // 改变打包模式 | |
target: 'node' // 进入 node 环境,默认是 web | |
} |
三、webpack 配置 babel-loader,兼容低版本 node
方案一:安装 babel-loader、@babel/core、@babel/preset-env(有时需要 @babel/plugin-transform-runtime 插件)
const path = require('path'); | |
module.exports = { | |
entry: './bin/core.js', | |
output: { | |
path:path.join(__dirname,'/dist'), | |
filename: 'core.js', | |
}, | |
mode: 'production', // 改变打包模式 | |
target: 'node', // 进入 node 环境,默认是 web | |
module: { | |
rules: [ | |
{ | |
test: /\.js$/, | |
use: { | |
loader: 'babel-loader', // 使用 babel-loader | |
options: { | |
presets: ['@babel/preset-env'], | |
plugins:[ | |
[ | |
'@babel/plugin-transform-runtime', | |
{ | |
corejs: 3, | |
regenerator: true, | |
useESModules: true, | |
helpers: true, | |
}, | |
], | |
] | |
}, | |
} | |
} | |
] | |
} | |
} |
方案二:node.js 实验功能
将 js 后缀更改为 mjs 后缀,即可(需要 node 版本为 14 以上), 小于 14 版本的时候,node 执行时需要加上 --experimental-modules 参数,例如:node --experimental-modules index.js
# npm
# 冷知识
npm dedupe
命令
该命令用于去重依赖和优化依赖树,
npm dedupe
命令会检查 node_modules 中的依赖树,并尝试将公共依赖上移,以减少冗余的副本,优化依赖树package-lock.json
文件
默认的package.json
文件中存在依赖版本兼容,导致后续依赖包升级或某些其他情况下,每一个环境中执行npm install
命令时,安装的依赖版本不同,这时候就有了package-lock.json
文件,它可以精确锁定版本号和依赖,并存储仓库地址信息,使得依赖安装更快.
最佳的实践方式是:把 package-lock.json 一起提交到代码库中,不需要 ignore。但是执行 npm publish 命令,发布一个库的时候,它应该被忽略而不是直接发布出去 (库项目一般是给其他仓库安装使用,不使用 lock 文件就可以复用主项目加载过的包)。npm ci
命令npm ci
命令会完全根据 package-lock.json 安装依赖,这可以保证整个开发团队都使用版本完全一致的依赖;执行npm ci
时,会先删除项目中所有的 node_modules 再安装.npm ci
永远不会改变package.json
和package-lock.json
文件
依赖是否出现在最终打包后的代码中取决于该依赖是否被引用devDependencies
开发依赖
用于指定生产依赖,该依赖不会被自动加载 (被别的模块依赖时)
并不是只有在 dependencies 中的模块才会被一起打包,而在 devDependencies 中的依赖一定不会被打包。实际上,依赖是否被打包,完全取决于项目里是否被引入了该模块。dependencies 和 devDependencies 在业务中更多的只是一个规范作用,我们自己的应用项目中,使用 npm install 命令安装依赖时,dependencies 和 devDependencies 内容都会被下载。peerDependencies
对等依赖
用于指定依赖于某些三方模块作为基础 (被别的模块进行加载时)peerDependencies
主要意义在于声明该模块无法脱离某个三方模块单独存在,而如果放在devDependencies
字段中就无法共享主模块中已下载的三方模块
使用场景有:插件不能单独运行,插件正确运行的前提是核心依赖库必须先下载安装,不希望核心依赖库被重复下载 等npm pack
打包命令npm pack
命令可以根据 package.json 中的bundledDependencies
字段和其他信息,将当前仓库打包为一个.tgz 格式的压缩文件,在发布到 npm 仓库之前可以用于本地功能测试、单文件离线下载、包的分发 (离线分发给团队成员使用) 等
通过 npm 安装此 tgz 包可以使用npm install package_name-1.0.0.tgz
来安装 tgz 包bundledDependencies
打包依赖bundledDependencies
打包依赖字段会被npm pack
命令所使用,当执行npm pack
命令时,会根据此字段将项目打包为几个模块 tgz 包.