Molecule 在构建工具中的选择

我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。

本文作者:修能

朝闻道,夕死可矣

何为 Molecule?
轻量级的 Web IDE UI 框架——Molecule
我们开源了一个轻量的 Web IDE UI 框架
Molecule实现数栈至简前端开发新体验

前言

构建通常指的是把源代码转换成发布到线上的可执行 JavaScrip、CSS、HTML 代码。在前端发展的过程中,源代码的模块体系在不断的更新,最终产物也在不断的更新。而随之也使得构建工具也在不断更新换代。

而目前来看,基于前端的细化领域下,针对不同领域下的构建工具也日新月异。来看看 Molecule 该如何选择构建工具呢?

Molecule 的需求

首先,我们需要分析 Molecule 对构建工具的需求有什么?

老版本的问题

  1. 本地开发和 build 的构建工具不同,不得不增加 web 命令来执行一个预览的任务,确保 build 后的产物没问题。
  2. 慢,由于使用 tsc 作为编译,所以编译较慢。
  3. 部分变量无法复用,导致重复定义。

代码编译

由于 Molecule 的代码是用 ESM 的模块书写,且 Molecule 面向的是 Web 应用。通常来说面向 Web 应用的依赖库是需要提供 ESM 的代码实现 tree shaking 的作用的。
所以我们这里需要把 ESM 书写的 Molecule 代码通过构建工具编译成 ESM。

思考:为什么要把 ESM 代码编译成 ESM?

  1. 将 TypeScript 编译成 JavaScript
  2. 将高级语法编译成低级语法

除此之外,由于我们考虑到 Node.js 后续发展以 Pure ESM 为主,且 Molecule 针对 CommonJS 的场景较少,故我们不考虑输出 CommonJS 的产物。

类型

需要支持输出类型。

样式

Molecule 中使用 BEM 作为类名规范,通常情况下使得需要在 Sass 中和 JavaScript 中都定义相同变量名。而类 Sass-in-JS 使得我们可以从 Sass 中导出变量名,在 JS 文件中使用。
这就使得构建工具不仅要支持 Sass 的编译,同时还需要支持插件,允许我们做 Sass-in-JS 的需求。

其他

其他相关文件,例如 JSON,PNG 等文件需要支持拷贝至相关指定目录。

调研构建工具

Webpack

Webpack 是目前构建工具中的老大哥了,作为顶级老牌构建工具,几乎所有场景都能适用。
缺点也仅仅是冗余代码较多,配置项太多,体积较大等。

Rollup

作为面向 JS 类库而出现的构建工具。其和 Webpack 相比,在打包后产生的冗余代码少,体积较小,功能专注。缺点仅仅是不支持 HMR。

Vite

直接排除

Parcel

Parcel 目前看作是面向 Web 应用的零配置,高速度的 Webpack。其有一个致命的弱点是,自定义插件支持不如 Webpack。这会让我们无法实现 Sass-in-JS。
2.0 可能有所改善,我不清楚。不予评价

swc

swc 在某种程度上,是 babel 和 tsc 的竞品,属于比较底层的构建工具。和 esbuild 同类型,只是 esbuild 基于 Go,swc 基于 Rust。

esbuild

extremely fast JavaScript Compiler

babel

很好,就是慢

tsc

很好,就是更慢。有一个优点,只有 tsc 能支持输出类型。

方案实施

由于大多数的构建工具都是 bundler,并不符合 Molecule 的定位。故采取的方案是 esbuild + Sass + tsc 的方案。
esbuild 取其作为 Compiler 的部分,Sass 取其编译 SCSS 文件的部分,tsc 负责编译出类型文件。

tsx 相关文件输出

 transformCtx = await esbuild.context({
        entryPoints,bundle: false,format: 'esm',outdir: dist,jsx: 'automatic',plugins: [
            {
                name: 'alias',setup(build) {
                    build.onLoad({ filter: /.*/ },async (args) => {
                        const source = await fs.promises.readFile(args.path,'utf8');
                        const contents = sassLoader(alias(source,args.path));
                        return {
                            contents,loader: args.path.endsWith('.tsx') ? 'tsx' : 'ts',};
                    });
                },},],});
    await transformCtx.watch();

做两件事

  1. 别名重定位
  2. 将文件中的样式文件改为 css

样式文件输出

/**
 *
 * @param {string} entry
 */
async function _transform(entry) {
    const res = await sass.compileAsync(entry);
    const regex = /^:export {(\n|.)+}$/m;
    const target = entry.replace(/src\//,'esm/').replace(/.scss/,'.css');
    const dirname = path.dirname(target);
    if (!fs.existsSync(dirname)) {
        fs.mkdirSync(dirname,{ recursive: true });
    }
    const css = res.css.replace(regex,'');
    fs.writeFileSync(target,css);
    if (regex.test(res.css)) {
        const exportModules = res.css.match(regex)[0];
        fs.writeFileSync(
            path.join(dirname,styleVariablesFileName),exportModules
                .replace(':export','export default')
                .replace(/: .*;/gm,(substring) => {
                    const stringLiteral = /(?<="|')\S+(?="|')/g;
                    if (!stringLiteral.test(substring)) {
                        const startIdx = substring.indexOf(':');
                        const endIdx = substring.indexOf(';');
                        return `:"${substring.substring(startIdx + 1,endIdx).trim()}",`;
                    } else {
                        return substring.replace(';',',');
                    }
                })
        );
    }
}

做两件事

  1. :export干掉
  2. :export的内容放到当前目录下的style__variables.js的目录中

类型文件输出

类型文件异步输出,防止阻塞

async function transformTyping() {
    typingCtx = spawn('tsc && (concurrently "tsc -w" "tsc-alias -w")',{
        stdio: 'inherit',shell: true,});
}

其他文件输出

/**
 *
 * @param {string} filePath
 */
function _copyFile(filePath) {
    const dest = filePath.replace(/src\//,'esm/');
    const dirname = path.dirname(dest);
    if (!fs.existsSync(dirname)) {
        fs.mkdirSync(dirname,{ recursive: true });
    }
    fs.createReadStream(filePath,'utf-8').pipe(fs.createWriteStream(dest));
}

遗留问题

  • 增量编译的问题
  • 代码压缩

欢迎大家就以上问题留言讨论!

最后

欢迎关注【袋鼠云数栈UED团队】~
袋鼠云数栈UED团队持续为广大开发者分享技术成果,相继参与开源了欢迎star

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件举报,一经查实,本站将立刻删除。

本文链接:https://www.f2er.com/3188568.html

大家都在看

  • 飞码LowCode前端技术系列:如何便捷快速验证实现投产及飞码探索

    本篇文章从数据中心,事件中心如何协议工作、不依赖环境对vue2.x、vue3.x都可以支持、投产页面问题定位三个方面进行分析。
    2023-11-16 博文
  • 如何优雅使用 vuex

    大纲 本文内容更多的是讲讲使用 vuex 的一些心得想法,所以大概会讲述下面这些点: Q1:我为什么会想使用 vuex 来管理数据状态交互? Q2:使用 vuex 框架有哪些缺点或者说副作用? Q3:我是如何在项目里使用 vuex 的? 初识 vuex 对于 vuex,有人喜欢,有人反感 喜欢的人觉
    2023-11-16 博文
  • 第三方组件及计算属性传参的问题解决方式

    1. 前言 唉,好想玩滋嘣。 2. 计算属性直接传参接收不到 表格数据某一列需要用的计算属性时,模板中使用计算属性 fullName 就会直接调用 fullName 函数,而在模板中 fullName(item) 相当于fullName()(item),此处为函数柯里化。 &lt;el-table-
    2023-11-16 博文
  • 记录--Vue3基于Grid布局简单实现一个瀑布流组件

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前言 在学习Grid布局之时,我发现其是CSS中的一种强大的布局方案,它将网页划分成一个个网格,可以任意组合不同的网格,做出各种各样的布局,在刷某书和某宝首页时,我们发现其展示方式就是一种瀑布流,是一种流行的网站页面布局,视觉表……
    2023-11-16 博文
  • 用强数据类型保护你的表单数据-基于antd表单的类型约束

    接口数据类型与表单提交数据类型,在大多数情况下,大部分属性的类型是相同的,但很少能做到完全统一。我在之前的工作中经常为了方便,直接将接口数据类型复用为表单内数据类型,在遇到属性类型不一致的情况时会使用any强制忽略类型错误。后来经过自省与思考,这种工作模式会引起各种隐藏bug,一定有更……
    2023-11-16 博文
  • pinia的使用

    前言 最近新开了个项目,以前老项目都是vue2+vuex开发的,都说用vue3+pinia爽得多,那新项目就vue3+pinia吧。这里记录一下pinia的使用。 使用方法 安装pinia: npm i pinia main.js中引入pinia: //main.js import { create
    2023-11-16 博文
  • 记录--让我们来深入了解一下前端“三清”是什么

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前端“三清” 在前端开发中,我们经常听到关于“三清”的说法,即 window、document、Object。这三者分别代表了 BOM(浏览器对象模型)、DOM(文档对象模型)以及 JS 的顶层对象。在这个体系中,我们通过 JavaScr
    2023-11-16 博文
  • 记录--啊?Vue是有三种路由模式的?

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 众所周知,vue路由模式常见的有 history 和 hash 模式,但其实还有一种方式-abstract模式(了解一哈~) 别急,本文我们将重点逐步了解: 路由 + 几种路由模式 + 使用场景 + 思考 + freestyle 路由概念
    2023-11-16 博文