一、模块化规范
1.1 CommonJs的特征
-
一个文件就是一个模块
-
每个模块都有单独的作用域
-
通过module.exports导出成员
-
通过require函数载入模块
但是commonJs是以同步方式加载模块的。
1.2 AMD
AMD是异步处理模块的一种规范
-
AMD使用起来相对复杂
-
模块JS文件请求频繁
1.3 ESModules 和 CommonJs相结合的规范
在浏览器中使用ESModule,在NodeJs中使用CommonJs规范
可以在package.json中添加type: ‘module’,这个会指定整个文件夹js文件使用ES Modules规范,如果在这个文件下想使用commonjs规范,就要文件后缀名改为.cjs
1.4 ESModules
-
通过在script标签上定义type = 'module’来定义ESModule
-
自动采用严格模式,忽略‘user strict
-
每个ESM都是单独的私有作用域
-
ESM都是CORS请求外部JS模块
-
ESM的script标签会延迟执行脚本(等页面渲染完,再执行JS文件)
导入、导出
import 和 export
-
export和import导入导出并不是对象的解构赋值,而是一种特殊的用法
-
并且export导出的只是值的引用,并不是导出值
// a.js
let name = 'aaa'
export { name }
// b.js
import {name} from './a.js'
- import 导入可以产生一个promise
import('./foo.js').then((module) =>{console.log(module)})
ES Modules in node
-
ES Modules 中可以导入CommonJs模块
-
CommonJs中不能导入ES Modules模块
-
CommonJs始终只会导出一个默认成员
-
注意import并不是解构导出对象
1.5 polyfill
cdn服务网址https://unpkg.com/
二、Webpack
webpack是一个模块化打包工具,通过loader去处理非javascript文件,plugins扩展
yarn add webpack@4.44.2 webpack-cli@3.3.12 -D
2.1 模块化打包工具的由来
-
ES Modules 每个模块都会请求,浪费请求资源
-
ES Modules 存在兼容问题
-
前端所有资源都需要打包
模块化打包工具就是解决这些问题的
2.2 webpack概要
-
webpack是一个模块化打包工具
-
通过loader编译代码
-
具有spliting code功能
-
前端项目中所有资源都可以才有模块化的方式引入
2.3 webpack使用
webpack是一款打包工具,通过loader加载编译资源文件,通过plugins实现除了加载资源以外其他自动化的操作。
webpack推荐根据代码的需要动态导入资源,因为真正需要资源的不是应用,而是正在编写的代码
-
逻辑合理,建立了代码与资源文件的依赖关系,Js确实需要这些资源文件
-
确保上线资源不确实,都是必要的。
学习新东西,要学习新思想,这样才能更好的提高。
对于大的文件就用file-loader,分离文件,对于小的文件可以用Data Urls的形式,打包到代码中。
2.4 webpack常用loader加载器分类
-
编译转换类
-
文件操作类
-
代码检查类
2.5 webpack核心工作原理
webpack会通过入口文件,推断出每个文件所需要的资源依赖关系,形成一个依赖关系树,然后交给rules去分析,哪个模块需要哪个loader处理。
2.6 loader实现的简单工作原理
const markd = require('marked')
module.exports = source => {
console.log(source)
let htmlStr = markd(source)
return `module.exports = ${JSON.stringify(htmlStr)}`
}
-
首先module.exports 函数
-
source变量接收资源文件
-
然后通过return 一个js代码字符串
最后webpack输出的代码为:
...
((module) => {
module.exports = "<h1 id=\"标题\">标题</h1>\n<h2 id=\"二级标题\">二级标题</h2>\n<blockquote>\n<p>哈哈哈</p>\n</blockquote>\n"
/***/ })
...
并且loader是通过流的方式进行的,一个loader处理完,可以将处理完的结果给另外一个loader处理,比如css-loader,style-loader
2.7 plugins
定义: 一个函数或者是一个包含apply方法的对象。
Webpack4不求人(5) ——编写自定义插件 - xialeistudio - 博客园
自定义插件工作流程:
Webpack在启动时,会实例化插件对象,在初始化compiler对象之后会调用插件实例的apply方法,传入compiler对象,插件对象会在aplly中注册响应的钩子,webpack执行过程中就会根据构建过程回应响应的钩子。
Compiler && Compilation对象
在编写Webpack插件过程中,最常用也是最主要的两个对象就是Webpack提供的Compiler和Compilation,Plugin通过访问Compiler和Compilation对象来完成工作。
-
Compiler 对象包含了当前运行Webpack的配置,包括entry、output、loaders等配置,这个对象在启动Webpack时被实例化,而且是全局唯一的。Plugin可以通过该对象获取到Webpack的配置信息进行处理。
-
Compilation对象可以理解编译对象,包含了模块、依赖、文件等信息。在开发模式下运行Webpack时,每修改一次文件都会产生一个新的Compilation对象,Plugin可以访问到本次编译过程中的模块、依赖、文件内容等信息。
常见钩子
Webpack会根据执行流程来回调对应的钩子,下面我们来看看都有哪些常见钩子,这些钩子支持的tap操作是什么。
钩子 | 说明 | 参数 | 类型 |
---|---|---|---|
afterPlugins | 启动一次新的编译 | compiler | 同步 |
compile | 创建compilation对象之前 | compilationParams | 同步 |
compilation | compilation对象创建完成 | compilation | 同步 |
emit | 资源生成完成,输出之前 | compilation | 异步 |
afterEmit | 资源输出到目录完成 | compilation | 异步 |
done | 完成编译 | stats | 同步 |
Tapable
Tapable是Webpack的一个核心工具,Webpack中许多对象扩展自Tapable类。Tapable类暴露了tap、tapAsync和tapPromise方法,可以根据钩子的同步/异步方式来选择一个函数注入逻辑。
- tap 同步钩子
- tapAsync 异步钩子,通过callback回调告诉Webpack异步执行完毕
- tapPromise 异步钩子,返回一个Promise告诉Webpack异步执行完毕
apply方法中插入钩子的一般形式如下:
compileer.hooks.阶段.tap函数('插件名称', (阶段回调参数) => {
});
简单的实例
// 移除webpack打包注释的插件
class MyPlugin {
constructor(options){}
apply(compiler){
// 插入钩子函数
console.log('MyPlugin enter')
compiler.hooks.emit.tap('MyPlugin', (compilation) => {
for( let 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
}
}
}
})
}
}
module.exports = MyPlugin
2.8 webpack-dev-server
webapck实现自动编译和自动刷新浏览器
yarn add webpack-dev-server -D
运行时就用 yarn webpack-dev-server
webpack-dev-server并没有产生磁盘的读写操作,每次编译和刷新都是读的内存中的代码
2.9 webpack开发时,设置访问静态资源目录
告诉服务器从哪里提供静态资源
devServer: {
contentBase: path.join(__dirname, "public"),
}
2.10 webpack配置代理
devServer: {
proxy: {
'/api': {
target: 'https://api.github.com/users',
pathRewire: { '^/api': '' },
changeOrigin: true,
}
}
},
2.11 Source Map
Source Maps其实就是打包之后的代码与源代码的映射关系
-
eval-是否使用eval执行模块代码
-
cheap- source map 是否包含行信息
-
module- 是否能够得到Loader处理之前的源码
个人开发的选择Sourc Map模式
开发环境: cheap-module-eval-source-map
生产环境: none
2.12 HMR (Hot Module Replacement)模块热替换
webpack-dev-server的问题:
自动打包刷新,会造成页面状态的丢失
HMR定义: 模块热替换功能会在应用程序运行过程中替换、添加、删除模块,无需重新加载整个页面。主要通过以下方式加快开发速度
-
保留在完全重新加载页面时丢失的应用程序状态。
-
只更新变更内容,以节约宝贵时间。
-
调整样式更加快速-几乎相当于在浏览器中修改样式。
开启HMR
2.13 DefinePlugin
DefinePlugin允许配置一个全局的常量
详见:DefinePlugin
2.14 Tree Shaking
定义: 移除javascript上下文中的未引用代码(dead-code)。
使用前提:webpack打包的代码必须使用ES Module(ESM)
最新版本的babel-loader并不会导致tree-shaking失效,可以通过查看源码来查
module.exports = {
...
optimization: {
usedExports: true, // 给dead-code做标记
minimize: true // 清除dead-code并压缩代码
},
}
2.15 concatenateModules合并模块
定义: 告诉webpack哪些片段可以安全的合并到一个模块中。在生产模式自动开启
module.exports = {
...
optimization: {
usedExports: true, // 给dead-code做标记
concatenateModules: true
},
}
2.16 sideEffects副作用
定义: 告知 webpack 去辨识 package.json
中的 副作用
标记或规则,以跳过那些当导出不被使用且被标记不包含副作用的模块。
-
一般用于npm的包标记是否有副作用
-
副作用是指,做了,除导出成员以外的事情
详见webpack 官方中文文档optimizationsideeffects
注意: 除非确定了整个项目中没有副作用,才会在package.json配置sideEffects:false,否则,就要配置sideEffects: []数组的形式,标记哪些文件没有副作用。
2.15 Code Splitting(代码分包/代码分割)
有两种
-
多入口打包
-
采用ESM的动态导入按需加载 采用import().then()的方式动态导入按需加载,webpack会自动分包按需加载
2.16 魔法注释
给分包的bundel添加名字,用法如下,可以给把不同的模块打包到一起
import(/* webpackChunkName: 'posts'*/ './posts/posts').then(({default: posts}) => {
//
})
2.17 MiniCssExtractPlugin
这个插件主要是将css提取到一个单独的css文件中,采用link的形式
一般css超过120k才会考虑单独拎出来,否则会适得其反。
提取之后需要OptimizeCssAssetsWebpackPlugin插件去压缩css文件
像这种压缩的可以配置optimization中的minimizer属性数组中,但是这样配置之后js代码就不会去压缩,需要重新安装TerserWebpackPlugin,配置在minimizer数组中。
配置minimizer中的好处就是只在需要压缩的时候才会调用插件,缺点就是css和js都要自定义安装插件去压缩
2.18 文件缓存问题,可以给文件配置hash
[contenthash] 文件变化就会更新掉文件名,最适合解决缓存问题
三、Rollup
3.1 定义
Rollup是一款ESM打包器,它的存在并不是为了webpack的竞争,而是为了提供一个充分利用ESM各项特性的高效打包器。
-
默认开启tree-shaking优化代码
-
Rollup 只引入最基本最精简代码,所以可以生成轻量、快速,以及低复杂度的 library 和应用程序
-
插件是rollup中的唯一途径
3 .2 Rollup和Webpack比较
rollup
优点:
-
输出结果更扁平,执行效率更高
-
自动移除未引用的代码
-
打包结果完全可以读,基本和手写代码一致
缺点:
-
加载非ESM的第三方模块比加复杂。
-
分包必须要要使用AMD模式,需要require.js去加载打包后的结果。
-
打包的后结果在立即执行函数里面,无法做HMR替换
四、规范化标准
eslint和stylelint、prettier、git-hooks(用Husky和lint-staged实现git hooks的需求)
eslint使用
在 .eslintrc.js中安装eslint-plugin-react 之后
module.exports = {
extends: [
'standard',
'plugin:react/recommended' // 继承eslint-plugin-react中的配置
],
}