资讯专栏INFORMATION COLUMN

精读《如何编译前端项目与组件》

jiekechoo / 1019人阅读

摘要:历史上由于是作为的替代品出现,当时要解决的问题是处理浏览器兼容问题,打包或,做一些公共资源替换,雪碧图等,最后可以顺带合并到一个文件,但模块化功能远远比弱,基本上只能合并,但不能理解模块概念。

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 是一个拥有优秀基因的前端组件库,是因为他遵循了前端组件最基本的代码素养:

编译后的代码全部符合基本 JS 规范,换个角度来说,使用 webpack 内置基本 js loader 就能完全解析。

将 css 代码抽离出来,这样不会强制项目对 node_modules 的代码应用 css-loader。

所以一个 靠谱的组件库 的产出文件,应该符合基本 ES 模块化规范,且不包括任何特殊语法。

但是这引发了一个新的问题:组件开发体验比项目差很多。

比如组件想使用雪碧图自动优化、想使用 worker-loader 方便快捷的调用多线程,想用自己的 css modules,甚至想把项目里一堆 PostCSS 快捷语法搬过来时怎么办?难道组件开发就不能获得与项目开发一样的体验吗?

要解决这个问题,笔者介绍一种基于 webpack 的通用构建方案,让本地调试、CDN 打包、ES6 -> ES3 转换 都使用统一套配置代码,同一套 loader。

2 精读

核心思想只有一句话:利用 webpack-node-externals 忽略 Webpack 对指向 node_modules 的 require 或 import 语句:

进行项目/组件调试时,开启 development 模式。

进行项目编译时,开启 production 模式。

进行组件编译时,开启 production 模式,且利用 webpack-node-externals 插件忽略 node_modules。

可以想像,根据第三条,如果所有组件都按照这个模式输出代码,那么 webpack 对 node_modules 编译时,只需要将所有 require 代码进行合并,不需要执行任何 loader,也不需要压缩,不需要 TreeShaking,因为这些在组件代码编译时全部已经做好了,这种构建效率几乎达到最大。

实际案例

我们拿支持 typescriptsasscss-modulesworker-loader 的场景作为案例。

我们创建三个文件 entry.tsx entry.worker.tsentry.scss

entry.scss:

.container {
  border: 1px solid #ccc;
}

.primary {
  color: blue;
  &:hover {
    color: green;
  }
}

entry.worker.ts:

import hello from "hello";

const ctx: Worker = self as any;

ctx.onmessage = event => {
  ctx.postMessage(hello());
};

export default null as any;

entry.tsx:

import * as React from "react";
import styles from "./entry.scss";
import * as MyWorker from "./parser.worker";

const worker = new MyWorker();

export default () => (
  
);

在上面三个文件中,我们分别利用了 Typescript 编译、SCSS 编译、css-modules 解析、worker-loader 解析(利用 webpack 自动生成字符串代码并利用 Blob URL 方式载入,这样就不需要创建新文件也可以用 worker 了,也不会存在跨域问题)。

为了支持这几个特性对如上代码做调试、项目发布、组件发布,我们分别看下这三个场景该如何配置编译脚本。

本地调试

本地调试是不用区分组件与项目的。因为无论何种情况,都需要进行基本的项目编译,载入所有自定义 loader 并打成一个 bundle 包。

此时我们只要维护一份 webpack 配置即可:

const webpackConfig = {
  mode: "development",
  module: {
    rules: [
      {
        test: /.worker.tsx?$/,
        use: {
          loader: "worker-loader",
          options: {
            inline: true
          }
        },
        include: path.join(projectRootPath, "src")
      },
      {
        test: /.tsx?$/,
        use: [
          [
            "babel-loader",
            {
              plugins: [
                [
                  "babel-plugin-react-css-modules",
                  {
                    filetypes: {
                      ".scss": {
                        syntax: "postcss-scss"
                      }
                    }
                  }
                ]
              ]
            }
          ],
          "ts-loader"
        ],
        include: path.join(projectRootPath, "src")
      },
      {
        test: /.scss$/,
        use: [
          "style-loader",
          [
            "css-loader",
            {
              importLoaders: 1,
              modules: true
            }
          ],
          "sass-loader"
        ],
        include: path.join(projectRootPath, "src")
      }
    ]
  }
};

export default webpackConfig;

利用这个配置加上 webpack-dev-server 即可完成组件与项目的本地调试。

项目发布

项目发布时,需要将所有代码打入到一个 bundle 包,此时只需使用 webpack-cli 即可,对配置做如下修改:

export default {
  ...webpackConfig,
  mode: "production"
};
组件发布

组件发布时,依然使用 webpack-cli 构建,但利用 webpack-node-externals 忽略对 node_modules 的解析。

import * as nodeExternals from "webpack-node-externals";

export default {
  ...webpackConfig,
  mode: "production",
  externals: [nodeExternals()]
};

此时编译的组件代码,包含了 Typescript 编译、SCSS 编译、css-modules 解析、worker-loader 解析,但所有 node_modules 代码都保持原样,比如下面的代码:

做了代码去重、按需加载、打包、压缩,但因为保持了 require 原样,因此大小只有源码体积。

同时上述三个场景都在复用 webpack 一套代码的基础上,利用了 webpack 的生态,因此维护性和拓展性都很强。后续再加入新功能,再也不需要到处找 babelgulp 的插件了!

3 总结

本文从 webpack 为切入点,但其实还可以从 parcelgulp 为切入点,实现前端项目、组件构建体系的统一。

不过从可定制性来看,webpack 插件生态更完善,所以笔者选择了 webpack

留下一个思考题:你的项目、组件是如何构建的呢?是用了一套代码,还是两套呢?

讨论地址是:精读《如何编译前端项目与组件》 · Issue #125 · dt-fe/weekly

如果你想参与讨论,请点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/101270.html

相关文章

  • 精读《Monorepo 的优势》

    摘要:引言本周精读的文章是。精读总的来说,虽然拆分子仓库拆分子包是进行项目隔离的天然方案,但当仓库内容出现关联时,没有任何一种调试方式比源码放在一起更高效。前端精读帮你筛选靠谱的内容。 1. 引言 本周精读的文章是 The many Benefits of Using a Monorepo。 现在介绍 Monorepo 的文章很多,可以分为如下几类:直接介绍 Lerna API 的;介绍如何...

    xcc3641 评论0 收藏0
  • 前端进阶资源整理

    摘要:前端进阶进阶构建项目一配置最佳实践状态管理之痛点分析与改良开发中所谓状态浅析从时间旅行的乌托邦,看状态管理的设计误区使用更好地处理数据爱彼迎房源详情页中的性能优化从零开始,在中构建时间旅行式调试用轻松管理复杂状态如何把业务逻辑这个故事讲好和 前端进阶 webpack webpack进阶构建项目(一) Webpack 4 配置最佳实践 react Redux状态管理之痛点、分析与...

    BlackMass 评论0 收藏0
  • 精读前端未来展望》

    摘要:精读前端可以从多个角度理解,比如规范框架语言社区场景以及整条研发链路。同是前端未来展望,不同的文章侧重的格局不同,两个标题相同的文章内容可能大相径庭。作为使用者,现在和未来的主流可能都是微软系,毕竟微软在操作系统方面人才储备和经验积累很多。 1. 引言 前端展望的文章越来越不好写了,随着前端发展的深入,需要拥有非常宽广的视野与格局才能看清前端的未来。 笔者根据自身经验,结合下面几篇文章...

    MadPecker 评论0 收藏0
  • 精读《使用 CSS 属性选择器》

    摘要:引言虽然现在与更流行,但使用它们会导致过分依赖滥用做唯一定位,违背了选择器的初衷。本期精读的文章是,带你重新理解强大的选择器。讨论地址是精读使用属性选择器如果你想参与讨论,请点击这里,每周都有新的主题,周末或周一发布。 1 引言 虽然现在 Css Module 与 Css-in-js 更流行,但使用它们会导致过分依赖 滥用 class 做唯一定位,违背了 Css 选择器的初衷。 本期精...

    zhangxiangliang 评论0 收藏0
  • 精读《手写 SQL 编译器 - 智能提示》

    摘要:经过连续几期的介绍,手写编译器系列进入了智能提示模块,前几期从词法到文法语法,再到构造语法树,错误提示等等,都是为智能提示做准备。 1 引言 词法、语法、语义分析概念都属于编译原理的前端领域,而这次的目的是做 具备完善语法提示的 SQL 编辑器,只需用到编译原理的前端部分。 经过连续几期的介绍,《手写 SQL 编译器》系列进入了 智能提示 模块,前几期从 词法到文法、语法,再到构造语法...

    ztyzz 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<