Molecule 在构建工具中的选择
我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。
本文作者:修能
朝闻道,夕死可矣
何为 Molecule?
轻量级的 Web IDE UI 框架——Molecule
我们开源了一个轻量的 Web IDE UI 框架
Molecule实现数栈至简前端开发新体验
前言
构建通常指的是把源代码转换成发布到线上的可执行 JavaScrip、CSS、HTML 代码。在前端发展的过程中,源代码的模块体系在不断的更新,最终产物也在不断的更新。而随之也使得构建工具也在不断更新换代。
而目前来看,基于前端的细化领域下,针对不同领域下的构建工具也日新月异。来看看 Molecule 该如何选择构建工具呢?
Molecule 的需求
首先,我们需要分析 Molecule 对构建工具的需求有什么?
老版本的问题
- 本地开发和 build 的构建工具不同,不得不增加 web 命令来执行一个预览的任务,确保 build 后的产物没问题。
 - 慢,由于使用 tsc 作为编译,所以编译较慢。
 - 部分变量无法复用,导致重复定义。
 
代码编译
由于 Molecule 的代码是用 ESM 的模块书写,且 Molecule 面向的是 Web 应用。通常来说面向 Web 应用的依赖库是需要提供 ESM 的代码实现 tree shaking 的作用的。
所以我们这里需要把 ESM 书写的 Molecule 代码通过构建工具编译成 ESM。
思考:为什么要把 ESM 代码编译成 ESM?
- 将 TypeScript 编译成 JavaScript
 - 将高级语法编译成低级语法
 
除此之外,由于我们考虑到 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();
做两件事
- 别名重定位
 - 将文件中的样式文件改为 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(';', ',');
                    }
                })
        );
    }
}
做两件事
- 把
:export干掉 - 把
: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
- 大数据分布式任务调度系统——Taier
 - 轻量级的 Web IDE UI 框架——Molecule
 - 针对大数据领域的 SQL Parser 项目——dt-sql-parser
 - 袋鼠云数栈前端团队代码评审工程实践文档——code-review-practices
 - 一个速度更快、配置更灵活、使用更简单的模块打包器——ko
 
Molecule 在构建工具中的选择的更多相关文章
- [翻译]在gulp构建工具中使用PostCSS
		
前言 PostCSS已经在一段时间内迅速普及,如果你还不知道PostCSS或还没有使用它,我建议你看一下之前的一篇介绍文章<PostCSS简介>,其中介绍了使用PostCSS的基本方法,包 ...
 - 前端构建工具-fis3使用入门
		
FIS3 是面向前端的工程构建工具.解决前端工程中性能优化.资源加载(异步.同步.按需.预加载.依赖管理.合并.内嵌).模块化开发.自动化工具.开发规范.代码部署等问题. 官网地址是: https:/ ...
 - Java构建工具:如何用Maven,Gradle和Ant+Ivy进行依赖管理
		
原文来自:https://zeroturnaround.com/rebellabs/java-build-tools-how-dependency-management-works-with-mave ...
 - 前端自动化构建工具--Gulp&&Webpack
		
前端构建工具的作用可以认为是对源项目文件或资源进行文件级处理,将文件或资源处理成需要的最佳输出结构和形式. 在处理过程中,我们可以对文件进行模块化引入.依赖分析.资源合并.压缩优化.文件嵌入.路径替换 ...
 - Build Tool(构建工具)
		
what: 构建工具能够帮助你创建一个可重复的.可靠的.携带的且不需要手动干预的构建.构建工具是一个可编程的工具,它能够让你以可执行和有序的任务来表达自动化需求.假设你想要编译源代码,将生成的clas ...
 - 取代 Maven?这款项目构建工具性能提升 300%
		
在 GitHub 上闲逛的时候,发现了一个新的项目:maven-mvnd,持续霸占 GitHub trending 榜单好几天了. maven-mvnd,可以读作 Maven Daemon,译作 Ma ...
 - Grunt和Gulp构建工具在Visual Studio 2015中的高效的应用
		
Grunt和Gulp构建工具在Visual Studio 2015中的高效的应用 Grunt和Gulp是Javascript世界里的用来做自动压缩.Typescript编译.代码质量lint工具.cs ...
 - Java  中三大构建工具:Ant、Maven和Gradle
		
Java世界中主要有三大构建工具:Ant.Maven和Gradle 目前:Ant已经销声匿迹.Maven也没落了,而Gradle的发展则如日中天. Maven的主要功能主要分为5点,分别是依赖管理系统 ...
 - ASP.NET5之客户端开发:Grunt和Gulp构建工具在Visual Studio 2015中的高效的应用
		
Grunt和Gulp是Javascript世界里的用来做自动压缩.Typescript编译.代码质量lint工具.css预处理器的构建工具,它帮助开发者处理客户端开发中的一些烦操重复性的工作.Grun ...
 - 如何选择JavaScript构建工具之Babel、Browserify、Webpack、Grunt以及Gulp
		
当我们开始一个新的 JavaScript 项目时,我们需要考虑的第一件事就是搭建一个前端编译环境.但是在面对众多的 JavaScript 构建工具时,我们却无所适从,不知道究竟哪一个才是最适合我们的. ...
 
随机推荐
- quarkus实战之八:profile
			
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本文是<quarkus实战>系列 ...
 - 【更新】【解决中文文件名乱码】mac一键获取最新datagrid 2017.3注册码到剪贴板
			
背景与前版实现请见: 前版原文 需要the unarchiver 解决中文文件名在mac上创建文件异常. 代码调整 IDEA_JIHUOMA_HOME="/tmp/idea-jihuoma& ...
 - 避坑 | OI排雷新生态
			
可持久化线段树 query 的时候两结点的 sz 相减的时候一定是左儿子相减. 可持久化线段树建新点要将原来的不更改的节点连接上. 动态规划也可以倒着考虑. P3957的单调队列是先加入后删除,否则加 ...
 - webpack是如何处理css/less资源的呢
			
上一篇文章 体验了webpack的打包过程,其中js文件不需要我们手动配置就可以成功解析,可其它类型的文件,比如css.less呢? css-loader 首先,创建一个空文件夹,通过 npm ini ...
 - form 表单恢复初始数据
			
1 表单数据的保存和恢复方法 1.1 前端数据保存方法 在前端,我们可以使用两种方法来保存表单数据:LocalStorage 和 Cookie. 使用 LocalStorage 保存数据:LocalS ...
 - Android 项目移植后运行黑屏
			
在做Android项目的移植过程中,即把名为a的代码和样式全部复制到b项目后,只是对a项目内多余代码删除和修改包名,最后替换完后,点击=>运行,运行成功,但是演示机显示黑屏,看log也没有报错. ...
 - 万字长文硬核AQS源码分析
			
阅读本文前,需要储备的知识点如下,点击链接直接跳转. java线程详解 Java不能操作内存?Unsafe了解一下 一文读懂LockSupport AQS简介 AQS即AbstractQueuedSy ...
 - 《SQLi-Labs》02. Less 6~10
			
@ 目录 索引 Less-6 题解 原理 Less-7 题解 Less-8 题解 Less-9 题解 原理 Less-10 题解 sqli.开启新坑. 索引 Less-6:布尔盲注,字符型[" ...
 - 《SQL与数据库基础》05. SQL-DCL
			
目录 DCL 用户管理 权限控制 本文以 MySQL 为例 DCL 用户管理 查询有哪些用户: 1. USE mysql; SELECT * FROM user; 2. SELECT * FROM m ...
 - 实现WebRTC群聊会议室(Mesh方案)
			
近期需要做一个类似会议室功能,但网络上大多数是一对一通信,故记录分享希望帮助到有用的人 WebRTC一对一聊天原理 关于WebRTC建立一对一聊天的模板网上很多,可参考以下博客:springboot+ ...