diff --git "a/89.\347\262\276\350\257\273\343\200\212\345\246\202\344\275\225\347\274\226\350\257\221\345\211\215\347\253\257\351\241\271\347\233\256\344\270\216\347\273\204\344\273\266\343\200\213.md" "b/89.\347\262\276\350\257\273\343\200\212\345\246\202\344\275\225\347\274\226\350\257\221\345\211\215\347\253\257\351\241\271\347\233\256\344\270\216\347\273\204\344\273\266\343\200\213.md" new file mode 100644 index 00000000..94025093 --- /dev/null +++ "b/89.\347\262\276\350\257\273\343\200\212\345\246\202\344\275\225\347\274\226\350\257\221\345\211\215\347\253\257\351\241\271\347\233\256\344\270\216\347\273\204\344\273\266\343\200\213.md" @@ -0,0 +1,222 @@ +# 1 引言 + +说到前端编译方案,也就是如何打包项目,如何编译组件,可选方案有很多,比如: + +- 通过 webpack / parcel / gulp 构建项目。 +- 通过 parcel / gulp / babel 构建组件。 + +如果你喜欢零配置的 parcel,那么项目和组件都可以拿它来编译。 + +如果你业务比较复杂,需要使用 webpack 做深度定制,那么常见组合是:项目 - webpack,组件 - gulp。 + +但项目与组件的编译存在异同点,不同构建工具支持的生态也存在异同点。 + +## webpack parcel gulp 生态的区别 + +- babel 一般不会解析模块,也就是一般仅做代码预处理,而不会改变文件结构,也对 require、import 语句不敏感。 +- webpack / parcel 主要就是解决模块化打包问题,因为浏览器还不支持(现在部分支持 `type="module"`)。 +- gulp 理论上可以将 babel、webpack、parcel 作为插件,但这是后来的事。历史上由于 gulp 是作为 grunt 的替代品出现,当时要解决的问题是处理浏览器兼容问题,打包 scss 或 less,做一些公共资源替换,雪碧图等,最后可以顺带合并到一个文件,但模块化功能远远比 webpack 弱,基本上只能合并,但不能 “理解模块概念”。 + +## 项目构建与组件构建的区别 + +项目构建的目的主要在于发布 CDN,所以大家一般不在乎构建脚本的通用性。换句话说,无论项目使用了怎样的构建方式,怎样理解 `import` 语句,甚至写出 `require.context` 等自定义语法,只要最终编译出符合浏览器规范的代码(考虑到兼容性)就足够。 + +组件构建的目的主要在于发布 NPM,除了 ESNext 规范会使用 Babel 编译成 ES3,大部分代码写的很收敛,甚至对 SASS 的使用都要与 Typescript 插件一起组合成复杂的 Gulp Task。 + +**所以往往大家会对项目采取复杂的构建约束策略,而对组件的编译采取相对简单的办法,确保发布代码的通用性。** + +所以在大部分项目使用 webpack 支持 worker-loader 时,编写组件时发现这段代码不灵了。或者至少你得付出一些代价,因为组件的调试依然可以利用 webpack-dev-server,这时可以加上 worker-loader,但由于 gulp 没有靠谱的 worker 插件,你的组件可能需要将 Worker 引用部分原样输出,希望由引用它的项目做掉对 worker-loader 的支持。 + +其实这种心态是很危险的,不仅导致了组件不通用,甚至引发了各构建工具的 Tree Shaking 优化。原因就是构建组件的代码太原始,冗余的代码没有删除,甚至直接引用的 SASS 代码仍然保留,更危险的是带上了一些特殊 webpack loader 才支持的语法。 + +之所以说 Antd 是一个拥有优秀基因的前端组件库,是因为他遵循了前端组件最基本的代码素养: + +1. 编译后的代码全部符合基本 JS 规范,换个角度来说,使用 webpack 内置基本 js loader 就能完全解析。 +2. 将 css 代码抽离出来,这样不会强制项目对 node_modules 的代码应用 css-loader。 + +所以一个 **靠谱的组件库** 的产出文件,应该符合基本 ES 模块化规范,且不包括任何特殊语法。 + +但是这引发了一个新的问题:组件开发体验比项目差很多。 + +比如组件想使用雪碧图自动优化、想使用 worker-loader 方便快捷的调用多线程,想用自己的 css modules,甚至想把项目里一堆 PostCSS 快捷语法搬过来时怎么办?难道组件开发就不能获得与项目开发一样的体验吗? + +要解决这个问题,笔者介绍一种基于 webpack 的通用构建方案,让本地调试、CDN 打包、ES6 -> ES3 转换 都使用统一套配置代码,同一套 loader。 + +# 2 精读 + +核心思想只有一句话:利用 [webpack-node-externals](https://github.com/liady/webpack-node-externals) 忽略 Webpack 对指向 node_modules 的 require 或 import 语句: + +1. 进行项目/组件调试时,开启 `development` 模式。 +2. 进行项目编译时,开启 `production` 模式。 +3. 进行组件编译时,开启 `production` 模式,且利用 [webpack-node-externals](https://github.com/liady/webpack-node-externals) 插件忽略 node_modules。 + +可以想像,根据第三条,如果所有组件都按照这个模式输出代码,那么 webpack 对 node_modules 编译时,只需要将所有 `require` 代码进行合并,不需要执行任何 loader,也不需要压缩,不需要 TreeShaking,因为这些在组件代码编译时全部已经做好了,这种构建效率几乎达到最大。 + +## 实际案例 + +我们拿支持 `typescript`、`sass`、`css-modules`、`worker-loader` 的场景作为案例。 + +我们创建三个文件 `entry.tsx` `entry.worker.ts` 与 `entry.scss`: + +**entry.scss:** + +```scss +.container { + border: 1px solid #ccc; +} + +.primary { + color: blue; + &:hover { + color: green; + } +} +``` + +**entry.worker.ts:** + +```tsx +import hello from "hello"; + +const ctx: Worker = self as any; + +ctx.onmessage = event => { + ctx.postMessage(hello()); +}; + +export default null as any; +``` + +**entry.tsx:** + +```tsx +import * as React from "react"; +import styles from "./entry.scss"; +import * as MyWorker from "./parser.worker"; + +const worker = new MyWorker(); + +export default () => ( +