前端工程化

前端工程化

模块化

背景:

在实际开发时,经常会遇到变量名称函数名称一样的情况。这不仅容易造成命名冲突,还会污染全局变量。

基于此,JS也引入模块化的概念:

  • 早期的模块化不是真正的模块化,立即调用函数表达式(简称IIFE)就是一个在定义时可立即执行的函数
var result = (function () {
    var name = "Barry";
    return name;
})();
// IIFE 执行后返回的结果:
result; // "Barry"

  • 后期的模块化才算是真正:目前Web开发倾向于ESMNode开发倾向于CJS
  • 在实际开发中,一个模块就是一个文件
  • 同步加载包括IIFECJS异步加载包括ESM。浏览器可兼容IIFE,服务器可兼容CJS,浏览器与服务器都兼容ESM
- CJS ESM
语法类型 动态 静态
关键声明 require exportimport
加载方式 运行时加载 编译时加载
加载行为 同步加载 异步加载
书写位置 任何位置 顶层位置
指针指向 this指向当前模块 this指向undefined
执行顺序 首次引用时加载模块 再次引用时读取缓存 引用时生成只读引用 执行时才是正式取值
属性引用 基本类型属于复制不共享 引用类型属于浅拷贝且共享 所有类型属于动态只读引用
属性改动 工作空间可修改引用的值 工作空间不可修改引用的值 但可通过引用的方法修改
  • 运行时加载指整体加载模块生成一个对象,再从对象中获取所需的属性方法去加载。最大特性是全部加载,只有运行时才能得到该对象,无法在编译时做静态优化。

  • 编译时加载指直接从模块中获取所需的属性方法去加载。最大特性是按需加载,在编译时就完成模块加载,效率比其他方案高,无法引用模块本身(本身不是对象),但可拓展JS高级语法(宏与类型校验)。

  • 使用type指定模块方案

    • package.json中指定typecommonjs,则使用CJS
    • package.json中指定typemodule,则使用ESM

mjs文件使用ESM解析,cjs文件使用CJS解析,js文件使用基于package.json指定的type解析(type=commonjs使用CJStype=module使用ESM)。

主流浏览器都能通过<script type=”module”>标签支持ECMAScript 模块 (ES modules) 。

ESM不再提供Node某些特性与不能灵活引用json文件了,因此__dirname__filenamerequiremoduleexports这几个特性将无法使用。

  • __filename__dirname可用import.meta对象重建
  • requiremoduleexports可用importexport代替
  • json文件的引用可用Fs模块readFileSyncJSON.parse()代替

CJS的循环依赖关系已通过缓存各个模块的module.exports对象解决,但ESM用了所谓的绑定。简而言之,ESM模块不会导出导入值而是引用值。

  • 导入引用模块可访问该引用但无法修改它
  • 导出引用模块可为引用该模块的模块重新分配值且该值由导入引用模块使用

四个babel子包

  • @babel/cli:提供支持@babel/core的命令运行环境
  • @babel/core:提供转译函数
  • @babel/node:提供支持ESM的命令运行环境
  • @babel/preset-env:提供预设语法转换集成环境

Stylelint、Eslint

Lint其实就是编辑器中运行的一个脚本进程,将代码解析为抽象语法树,遍历抽象语法树并通过预设规则做一些判断与改动,再将新的抽象语法树转换为正确代码。

  • Tslint官方已宣布废弃tslint,改用eslint代替其所有校验功能
  • eslint部分配置与prettier部分配置存在冲突且互相影响,为了保障格式化性能就放弃接入prettier

所以VSCode只需安装StylelintEslint两个插件。

# stylelint及其依赖
npm i stylelint stylelint-config-standard stylelint-order postcss-html postcss-scss postcss-less
# eslint及其依赖
npm i @babel/core @babel/eslint-parser @babel/preset-react eslint eslint-config-standard eslint-plugin-html eslint-plugin-import eslint-plugin-n eslint-plugin-promise eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-vue vue-eslint-parser
# typescript-eslint及其依赖
npm i -D @typescript-eslint/eslint-plugin @typescript-eslint/parser typescript eslint-config-standard-with-typescript

安装完毕需配置多份文件,

  • CSS类型html/css/scss/less/vue文件
  • JS类型html/js/ts/jsx/tsx/vue文件
  • 查看插件文档,Stylelint的配置文件可同时校验html/css/scss/less/vue文件
  • Eslint需配置不同文件分别校验html/js/ts/jsx/tsx/vue文件
  • 两个插件可在settings.json中通过指定字段覆盖默认配置。
  • 配置文件中的rules可根据自身编码规范编码风格适当调整
  • 配置Stylelint可查看Stylelint规则
    配置Eslint可查看Eslint规则
    配置TypeScriptEslint可查看TypeScriptEslint规则
    配置VueEslint可查看VueEslint规则

Webpack

缩减范围

  • 配置include/exclude缩小Loader对文件的搜索范围,好处是避免不必要的转译

  • include/exclude通常在各大Loader中配置,src文件夹通常作为源码目录

export default {
	// ...
	module: {
		rules: [{
			exclude: /node_modules/,
			include: /src/,
			test: /\.js$/,
			use: "babel-loader"
		}]
	}
};

缓存副本

  • 配置cache缓存Loader对文件的编译副本,好处是再次编译时只编译变动的文件
  • 很多Loader/Plugin都会提供一个可用编译缓存的选项,通常包括cache字眼
import EslintPlugin from "eslint-webpack-plugin";

export default {
	// ...
	module: {
		rules: [{
			// ...
			test: /\.js$/,
			use: [{
				loader: "babel-loader",
				options: { cacheDirectory: true }
			}]
		}]
	},
	plugins: [
		// ...
		new EslintPlugin({ cache: true })
	]
};

定向搜索

  • 配置resolve提高文件的搜索速度,好处是定向指定所需文件路径

  • 若某些第三方库以默认形式引用可能报错或希望程序自动索引指定类型文件都可通过该方式解决

  • alias表示映射路径,extensions表示文件后缀,noParse表示过滤无依赖文件。

  • 通常配置aliasextensions就足够。

export default {
	// ...
	resolve: {
		alias: {
			"#": AbsPath(""), // 根目录快捷方式
			"@": AbsPath("src"), // src文件夹快捷方式
			swiper: "swiper/js/swiper.min.js"
		}, // 导入模块快捷方式
		extensions: [".js", ".ts", ".jsx", ".tsx", ".json", ".vue"] // 导入模块省略后缀
	}
};

摇树优化

删除项目中未被引用代码,好处是删除重复代码与未使用代码

摇树优化只对ESM生效,对其他模块规范失效。摇树优化针对静态结构分析,只有import/export才能提供静态的导入/导出功能,因此在编写业务代码时必须使用ESM才能让摇树优化删除重复代码与未使用代码。

webpack中只需将打包环境设置为生产环境就能让摇树优化生效,同时业务代码使用ESM编写,使用import导入模块,使用export导出模块。

export default {
	// ...
	mode: "production"
};

按需加载

将路由页面/触发性功能单独打包为一个文件,使用时才加载,好处是减轻首屏渲染的负担。因为项目功能越多其打包体积越大,导致首屏渲染速度越慢。

首屏渲染时只需首页的JS代码而无需其他网页的JS代码,所以可用按需加载实现。webpack v4+提供模块按需切割加载功能,配合import()可做到首屏渲染减包的效果,以加快首屏渲染速度。只有当触发某些功能时才会加载当前功能的JS代码

const Login = () => import( /* webpackChunkName: "login" */ "../../views/login");
const Logon = () => import( /* webpackChunkName: "logon" */ "../../views/logon");

作用提升

分析模块间依赖关系,把打包好的模块合并到一个函数中,好处是减少函数声明与内存花销

在未开启作用提升前,构建好的代码会存在大量函数闭包。因为模块依赖,通过webpack打包后会转换为IIFE,大量函数闭包包裹代码会导致打包体积增大,模块越多越明显。在运行代码时创建的函数作用域变多,导致更大的内存开销。

在开启作用提升后,构建好的代码会根据引用顺序放到一个函数作用域中,通过适当重命名某些变量以防止变量名冲突,以减少函数声明与内存花销。

webpack中只需将打包环境设置为生产环境就能让作用提升生效,或显式设置concatenateModules

export default {
	// ...
	mode: "production"
};
// 显式设置
export default {
	// ...
	optimization: {
		// ...
		concatenateModules: true
	}
};

压缩资源

压缩HTML/CSS/JS代码,压缩字体/图像/音频/视频,好处是更有效减少打包体积。极致地优化代码都有可能不及优化一个资源文件的体积更有效。

针对HTML代码,使用html-webpack-plugin开启压缩功能。

import HtmlPlugin from "html-webpack-plugin";

export default {
	// ...
	plugins: [
		// ...
		HtmlPlugin({
			// ...
			minify: {
				collapseWhitespace: true,
				removeComments: true
			} // 压缩HTML
		})
	]
};

单元测试

expect与test

单元测试有两个很重要的概念函数,分别是expect()test()expect()表示期望得到的运行结果,简称期望结果;test()表示测试结果是否通过预期,简称通过状态。

Learn

lerna显式地改变项目结构,把多个子包合并为一个仓库,然后使用packages文件夹存放每个子包,以使用一个仓库管理多个子包。

掌用命令

命令 功能 描述
lerna init 初始项目
lerna boostrap 安装依赖 自动解决子包间的依赖关系 子包内部互相依赖会使用软链接处理
lerna clean 卸载依赖 只能卸载每个子包的node_modules 不能卸载根目录的node_modules
lerna create <name> 新增子包 packages文件夹中创建由lerna管理的子包
lerna add <pkg> 安装模块 为所有子包安装模块 可通过--scope=pkg安装指定子包模块
lerna run <script> 执行命令 为所有子包执行命令 可通过--scope=pkg执行指定子包命令
lerna version 标记版本 标记存在修改行为的子包
lerna publish [bump] 发布子包 发布全部private不为true的子包
posted @ 2022-10-24 14:55  一星一辰  阅读(88)  评论(0编辑  收藏  举报