Skip to main content

· 7 min read

在2021年的新春之际,我们也在忙碌地准备着reSKRipt V4版本的发布,在自身重构、与社区协作的多重努力之下,最终完成了这个最为关键的内部重构版本,很高兴向大家介绍变化并不那么大但又处处全新的V4。

对于想快速了解版本间的差异并更新版本的开发者,请参考V4迁移手册。V4版本的最大变化是各类配置(项目配置reskript.config.js、入口配置entries/*.config.js)的调整,我们已经在近10个项目中进行了验证性的升级,以确保大多数项目可以低成本地成功升级。

代号

我们依旧延续着为每个版本起一个代号的“执着”,在V4中,最大的变化是我们对社区发展的研判,对新潮的ESM格式的引入,因此我们借用“日新月异”一词,以“Modern”为引,为V4版本赋了一个相对拗口的代号:

V4 - Moden Module - 时新模异

起缘

经常写Node项目的朋友可能会有所了解,随着Node 12开始对ESM模块的原生支持,在Node社区正在逐渐兴起向ESM模块转换的势头,其中Sindresorhus更是以一己之力,将自己发布的近千个流行的NPM包一一升级为ESM格式,并拒绝CommonJS格式的代码调用它们。

为了避免错误地引用仅支持ESM的包导致运行时的错误,在V3的实现中,我们有一个scripts/check-deps.mjs文件用于检查依赖版本是否正确。随着时日的积累,这个文件在最终有着这样的代码:

const RESTRICTED_DEPENDENCIES = [
['imagemin', '7.x'],
['img-loader', '3.x'],
['log-symbols', '4.x'],
['ora', '5.x'],
['globby', '11.x'],
['unified', '9.x'],
['remark-gfm', '1.x'],
['remark-parse', '9.x'],
['remark-stringify', '9.x'],
['find-up', '5.x'],
['pad-stream', '2.x'],
['internal-ip', '6.x'],
['matcher', '4.x'],
['pkg-dir', '5.x'],
['chalk', '4.x'],
['execa', '5.x'],
['matcher', '4.x'],
['tailwindcss', '2.x'],
];

将近20个依赖已经受ESM的制约而无法升级到最新版本,并且这个数量还在持续地增长。我们渐渐意识到情况并不像想象地那么乐观,如果继续依附在CommonJS格式上,无论有多少理由支持,我们都将与社区渐行渐远,享受Node社区的生态会变得越来越难。

因此,我们在2021年末,下了一个决心,将reSKRipt的内部实现整体迁移到ESM格式上。所幸reSKRipt自身是一个命令行的工具,几乎不被我们的用户通过函数调用的方式使用,这也使得我们可以通过内部的重构,在尽可能减少对用户影响的情况下实现架构的升级。

变化

在经历了大约3个月的重构之后,我们终于实现了reSKRipt的几乎全盘ESM化,当前仅有@reskript/eslint-plugin@reskript/config-jest两个包受上游(eslintjest)的约束依旧保留着CommonJS的格式。这是我们本次重构的代码修改量:

377 files changed, 10289 insertions(+), 7054 deletions(-)

我们几乎修改了每一个文件,用万行以上的代码调整,确保了ESM模块的可用性、所有依赖的更新,摆脱了依赖版本的限制:

const RESTRICTED_DEPENDENCIES = [
// 终于所有依赖都能最新了哈哈哈!!!
];

除此之外,我们也借着这一次对所有代码的重新审视、调整,引入了不少实用的功能:

  1. 我们支持了项目配置和入口配置文件的强类型化,现在你可以使用TypeScript编写配置,通过@reskript/settings导出的configure函数可以获得强大的类型提示和检验。
  2. 在配置中,支持plugins数组传递空值和嵌套数组,这将便于你在不同的系统、流水线环境中使用不同的插件集合。
  3. 配置中的各个finalize函数也升级为了异步函数,你将获得更大的灵活性,甚至可以基于远程的配置来调整reSKRipt的输入与输出。
  4. 仰仗于各个环节的进一步异步化,我们在查找Webpack Loader等环节使用了并发的异步文件操作,为你带来构建速度上的提升。
  5. 更进一步地,V4版本支持自定义项目配置文件的路径,你可以使用skr build --config指定配置文件,一个项目多个配置输出不同的产物将不再有任何阻碍。

相信这些优化将支持你更便捷、高效地使用reSKRipt来赋能项目的研发效率。

后续

如果你在之前看过V4迁移手册,可能会发现我们的配置文件格式做了一个重大的调整,通过configure函数导出配置时,增加了一个额外的参数:

export default configure(
'webpack',
{
// ...
}
);

是的,Webpack不再是我们唯一的构建选择,社区也在飞速地发展,新的、差异化的工具开始在舞台上活跃。在完成这一次重构后,我们将把重心放在引入Vite上。我们的目标是将reSKRipt打造成一个“双擎”工具,同时在builddevplay等命令上支持Webpack和Vite作为底层驱动,让你可以根据项目的粒度、类型、团队等因素自由地选择最合适的工具。

· 7 min read

在2022年的伊始,很荣幸向大家介绍reSKRipt V3的更新。

对于想快速了解版本间的差异并更新版本的开发者,请参考V3迁移手册。V3的变更非常有限,相信你可以很快地完成升级。

代号

我们之前说过,出于唯心的考虑,我们会为每一个版本起一个代号。事实上,V3版本虽然仅有很小的变更,但它的孕育是一个非常曲折的过程,在此,我们为它赋予一个最能表达的代号:

V3 - Slow Life - 闲生慢活

诞生

自V2在2021年8月发布以来,稳定的迭代更新支持着数百个项目的构建。但随着社区一些工具的更新,我想每一个使用reSKRipt的开发者或多或少见到过这样的提示:

=============

WARNING: You are currently running a version of TypeScript which is not officially supported by @typescript-eslint/typescript-estree.

You may find that it works just fine, or you may not.

SUPPORTED TYPESCRIPT VERSIONS: >=3.3.1 <4.5.0

YOUR TYPESCRIPT VERSION: 4.5.4

Please only submit bug reports when using the officially supported version.

=============

又或者在使用VSCode的StyleLint插件时见到过这样的警告:

vscode-stylelint 1.x expects to use Stylelint 14 at minimum. Usage with prior versions of Stylelint is no longer supported. While older versions may continue to work for a while, you may encounter unexpected behaviour. You should upgrade your copy of Stylelint to version 14 or later for the best experience.

在这一段时间里,社区的两大代码检查工具ESLint和StyleLint都发布了大版本的升级,而出于兼容用户本地安装的版本,reSKRipt一直没有跟进这些升级。

在原定的计划中,我们计划将ESLint和StyleLint的升级与更多的功能和内部实现一同发布,形成一个更有冲击力的版本。但我们也发现,为了自身的研发节奏,长久地将我们的用户暴露在这些警告和提示之下,是一种不负责任。

所以,我们在思索许久之后,决定放慢我们的研发节奏,将大版本拆分为多个阶段,更早地将有用的内容推送给大家。当然这也可能让大家更频繁地看到大版本的变化而产生一些不安感,但我们依然愿意相信,伴随着@reskript/doctor migrate功能,我们可以提供更友好、流畅的更新体验。

因此,我们选择将最为核心的工具升级发布为一个单独的V3版本,同时借此希望给大家一份“轻松愉快地升级并享受最新工具链”的功能,这也正是我们寄托在“慢生闲活”之上的愿望。

后续

前面有进到,V3实际上是一个大版本拆分后的发布,在这个时间点上,我们也很高兴向大家同步后续几个大版本的路线图。

我们计划在Q1继续发布2个大版本。

在2月初,即春节后,我们将发布V4版本。V4版本将是一次内部实现的重大调整,我们会在这个版本中实现纯ESM格式版本用TypeScript编写配置文件等重要特性,将开发和构建真正带入现代化的进程。

随后的3月底,我们将发布V5版本。V5版本将会引入一个相对基础的Vite的实现。我们决定在这个版本同时兼容Webpack与Vite,由开发者自主选择其一作为开发、构建的底层工具。兼容并抽象多个工具并不是一件简单的事,因此我们也会在这个版本引入较大的配置文件结构的变更,同时我们将尽可能地保持没有深入定制的产品可以低成本切换到Vite生态之上。假如你没有使用各种finalize配置,并且没有自定义HTML模板,相信迁移将会是一件愉快的事情。

在此之后,我们计划在V5上稳定观察大约半年左右,并有Q3底针对Vite做出稳定性、性能的优化后发布V6版本。并且我们也将在这个版本决定是否还保留Webpack的实现。

在这些版本推进的同时,我们也将积极思考如API Mock、WASM图片压缩、强化Bundle分析等话题,争取将reSKRipt打造为真正有别于社区的,一体化的优秀工具。

祝愿

2022年初是对中国的互联网来说并不温暖的一个冬天,我们也通过这个版本,祝愿大家能抛开一些焦虑与紧张,有一个自己期望的生活节奏。

我们强烈地推荐你升级到V3版本,如果在此期间遇到任何的困难,欢迎通过Github向我们咨询

· 12 min read

很高兴在这一刻向大家介绍reSKRipt V2版本的正式发布。

如果你想快速了解如何升级到V2,那么可以参考V2迁移手册,此文更多地用于介绍我们的心路历程和V2的版本规划。

唯心 - 关于代号

reSKRipt在GitHub上的第一个提交始于2021年1月24日,与今天(2021年8月26日)几乎正好7个月。为了纪念这个值得庆贺的日子,也为了更好地鼓舞我们自己向前迈进,我们决定给reSKRipt的每个大版本一个贴心的称号。我们的历史版本也将按相应的代码命名并永久保存。

作为一个已经结束使命的版本,我们也为V1版本起了一个代号:

V1 - Preserved Heart - 坚韧之心

在开源在GitHub之前,作为一个团队内部的工具,reSKRipt早已在内部的源码平台上孵化了3年之久,有过近百个版本。把一个团队高度一致的技术栈下诞生的工具开放出来,甚至推进成为一个顶级互联网公司的代码库初始化工具,是一件需要勇气和坚持的事情。

在这不长不短的3年时间里,我们经历了很多的升级、迁移、需求、质疑,最终为工具的统一与改进埋下了一株深根,即使是自夸,我们也相信这份坚韧的心,值得保留在代码的历史之中。

另一方面,V1作为初始之作,固定了整个工具的流程、逻辑、架构,从发展的角度看,亦不谓于“核心”的定位。在我们进一步选择更现代化的工具如Vite、ESBuild、SWC之前,V1留下的财富将会贯穿于reSKRipt的生命。

因此,V1谓之于“坚韧”,谓之于“心”。而V2版本,也被赋予了相应的代号:

V2 - Long Awaited - 望之如梅

V2相关的第一个议题始于2021年4月,距V1的发布仅3月之久。当然很遗憾地ESM化并没有进入到V2版本中。自那时开始,我们就一直在等待一个成熟、合适地机会,推动V2版本的诞生与发布,其计划的时间线之长久,称为“望梅”当之无愧。

“望之如梅”一词取自于“望梅止渴”,意在表达我们长时间对它的期待,以及身体力行地不断演进向其靠拢。历经4个月的时间,终于在webpack-dev-server发布4.0.0之际,让其落于地面,与所有用户相见。

我们也相信,在V2版本中带来的变化,同样是所有用户所期盼、所需要的,正如干渴之时的一棵梅果,虽不及最先进的那些工具一样饮之如怡,却也足够让开发与构建更为舒心。

契机 - 社区迭代

我们在生产工具的时候,一向尊重大版本的重要性。如果无限制地追求自身的整洁而发布大版本来淘汰一些瑕疵,定会失信于用户。因此我们对于大版本的规划,往往会与社区相应工具的版本对齐,并以此为契机抖落原本加于自身的包袱,也同时给用户提供一个低成本的升级路径。

V1版本的契机无疑是Webpack 5.0的发布,基于Webpack新增的持久缓存能力,我们将编译时间普遍缩短了2/3以上,10万行规模的项目也可以轻松在3分钟内完成整个构建过程。

而V2的发布,依然立足于社区之上,引入了非常关键的一个更新:webpack-dev-server的4.0版本。你可以在这里看到官方发布的4.0的升级手册。当然大部分的变化已经被reSKRipt内部封装屏蔽,绝大部分用户无需关心它的破坏性变化。我们需要了解的是,webpack-dev-server 4.0版本为我们带来了更稳定的热更新能力、更合理的日志架构。

当然更新一个社区的核心工具并不是一件容易的事,有不少的第三方插件可能因此而无法工作。为此,我们投入了大量的工作,例如重新定制并实现了skr dev的进度条功能,以此致力于提供一致、优秀的开发体验。

轻装 - 清理债务

是的,每一个大版本都必然会产生一系列的破坏性变更,同时也是一次清理自身的债务与负担的机会。

我们在V2版本之初,就大幅度调整了项目的构建和配置,这是“内功”的修为。相比于V1,我们全盘TypeScript化了测试用例、引入了一个用于集成测试的五脏俱全的“微型”TodoMVC项目,大面积地清理了历史的代码。

同样地,我们也借此废弃了一部分的配置和参数,并以功能更强、更加正交的能力给予替代,你依然可以通过V2迁移手册查阅所有的这些变化。

革命 - 全面异步

如果说参数、配置的变更是我们在工具层面上的进化,那么V2真正最重要的破坏性变更,则是对外暴露的API的全面异步化。

在V1版本时,我们希望reSKRipt能全面地兼容于社区的各类构建工具链,比如electron-forgeelectron-webpack等,因此诸如@reskript/config-webpack暴露的createWebpackConfig等API都是同步的,在那个时代,我们的心态是被动的,既然社区有工具只能使用同步的API来对接,我们就提供同步的API。

但最终我们发现,如果禁锢自己永远无法达到自身期待的那个理想之乡,因此我们决定投身于社区的建设。如果社区的主流工具无法对接异步的API,我们就去贡献相关的逻辑,让整个社区也能受益于异步的能力。我们已经electron-forge提交了相关议题作为我们迈出的第一步。

在全面的异步化之后,我们得到了大量的收益,几乎所有的文件检查、读取都可以并行化。例如计算Webpack缓存标识符的逻辑,需要读取5+个文件并合并计算哈希,在异步化之后,我们可以并行地处理这些文件,来获得不小的性能优势。

异步化也同样为我们打下了开拓未来的基石,我们相信随着工程的思想发展,构建这个环节会有越来越多的机器对源码的解读,这一切都会在未来产生大量的源代码的读取、解析、汇聚,而异步解除了我们串行IO的性能的担忧,便于在未来更专注地投入工具能力的升级。

精耕 - 事关体验

作为一个开发者的工具,我们坚持开发者体验是评价它的最重要的指标之一。因此我们在V2的开发中也加入了大量开发者体验上的提升。

首先,我们完全重写了子命令的管理与调用逻辑,现在你可以不安装任何的@reskript/cli-*子命令包,单纯地通过skr --help看到所有可用的子命令。

我们也进一步支持了子命令的自动安装,假设你没有安装@reskript/cli-dev,在执行skr dev时经过友好地询问,我们会自动为你装上相应的包,并顺利地将命令运行起来。

我们同样追求更好地适配社区的工具,skr test从V2版本开始可以将你的参数全部传递到jest之上,以发挥出jest完整的能力。

我们还从V2版本开始支持了TailwindCSS,你可以通过简单的配置开启使用tailwind进行样式开发。

我们用clipanion作为新的命令行编排框架,并将在未来不久带来命令的自动补全等实用能力,皆在提供最佳的使用体验。

我们也同样关心你的升级体验,现在你可以通过在项目根目录下执行npx @reskript/doctor migrate-v2来进行自动化地检测,尽可能地发现需要修改的内容。同时你也可以暂时停留在V1版本,通过export SKR_FLAGS=deprecation-error来发现在V2中会被废弃的参数的使用。

结语 - 始终相伴

在你看到这篇博客之际,V2版本已经经过了5+个beta版本,在内部10+个产品上完成了验证,这些产品包括了普通项目、monorepo仓库、微前端架构等等复杂的场景,我们有信心为大家交付一个高质量的初始版本。

我们欢迎并推荐你升级到V2版本,如果在此期间遇到任何的困难,欢迎通过Github向我们咨询

· 2 min read

在2021年8月19日,webpack-dev-server发布了4.0.0版本。作为reSKRipt计划中的一部分,我们将在这个时间点开始准备2.0.0版本的升级。

我们将会使用一周的时间,即在2021年8月26日左右冻结所有1.x版本的新功能需求,针对1.x版本后续仅接受问题修复,并在约6个月后逐渐停止对问题的修复支持。

我们计划2.0.0版本的主要改动是支持webpack-dev-server@4和一些当前标记为废弃的功能的清理,预期升级的成本不高。

为了更好地支持你升级到未来的版本,我们在1.15.0版本中推出了一项新的功能。现在你可以通过SKR_FLAGS=deprecation-error这一环境变量,来要求skr命令遇到废弃功能时直接报错退出。当然在命令行上依然会提示你相应的修改点和替代功能。

SKR_FLAGS将是一个长期存在且重大的功能,我们计划在未来支持更多的标记让reSKRipt有更丰富的定制行为,在当前,我们建议你全局配置该环境变量以打开相关的标记:

export SKR_FLAGS=all

关于标记的具体功能和使用方式,可以参考命令行介绍文档

· One min read

reSKRiptv1.13.0起,增加了对Reflect.metadata的支持,对应TyepScript中的emitDecoratorMetadata配置项。

考虑到这一功能会生成额外的代码,而大部分应用并不需要,我们将它隐藏在了build.uses配置项中,具体可参考特殊第三方库的优化章节的说明。

如果你从现在开始希望使用这一功能,可以将你的项目配置文件修改为以下内容:

exports.build = {
uses: ['lodash', 'reflect-metadata'],
};

需要注意的是,如果你原本没有build.uses配置,那么自定义该配置会导致默认值(['lodash'])失效,因此需要自己重新补充上这2个值。

· 4 min read

在使用reSKRipt的时候,我们非常推荐使用skr play命令来调试单个组件,它会为你提供一个可实时编程的界面,实时看到对应组件的可交互界面。

但并不是每一个组件都纯粹到可以没有外部依赖就跑起来的,其中很典型的一种情况就是组件依赖了路由,例如我们需要用useParams()来获取URL中的参数,或使用useLocation()拿到具体的路径信息。由于skr play默认的调试环节是不具备路由等环境的,因此需要我们做一些额外的配置工作。

目录结构

我们假定正在开发一个Foo组件,它需要使用react-router-dom才能正常展示,那么可以有下面这样的目录结构:

/src
/components
/Foo
/__repl__
index.play.tsx
index.tsx

其中src/components/Foo/index.tsx为组件的实现代码。我们可以看到这个结构中多了一个src/components/Foo/__repl__/index.play.tsx文件,这个被称为skr play的配置文件。这个文件的名字与组件文件名同名,如果你的组件文件名是Bar.tsx,那么它就是__repl__/Bar.play.tsx

注入外部依赖

__repl__/index.play.tsx中,我们可以编写以下内容:

import {MemoryRouter} from 'react-router-dom';

export const provides = {
MemoryRouter,
};

可以看到,我们引用了react-router-dom中的MemoryRouter,并通过export const provides对象把它导了出去。

在配置文件中,export const provides导出可以将你需要的内容“带给”skr play的调试现场。

使用依赖

当你使用skr play src/components/Foo/index.tsx打开调试现场时,你看到的代码中依然没有MemoryRouter,此时你还是不能正确地看到组件的长相。你需要将左侧编辑器中的代码调整为这样:

function Repl() {
return (
<I.MemoryRouter initialEntries={['/foo/bar/123']}>
<Foo />
</I.MemoryRouter>
);
}

简单来说,所有你在配置文件的provides对象里提供的东西,都可以在界面中用I.xxx拿来用。

当然,为了调试方便,也可以把Link也提供出去,在调试界面里加几个链接来快速跳到某些URL下看效果。

总结

我们介绍了通过__repl__/xxx.play.tsx配置文件导出依赖给调试现场的方法,重点在于:

  1. 有一个__repl__/xxx.play.tsx作为skr play的配置文件,与组件一一对应。
  2. 使用export const provides对象提供依赖。
  3. 在调试现场使用I.xxx使用依赖。

除了路由,你也可以用相同的方法解决如Context等问题。

· 4 min read

在之前版本的reSKRipt中,你可以通过ReactComponent这一命名导出中获得一个SVG转换出来的组件:

import {ReactComponent as IconClose} from './close.svg';

<IconClose />

reSKRipt v1.13.0开始,这个规则将发生一些改变。

背景

在Webpack的发展历史上,url-loader是负责将一个资源转为一个引用它的URL的准官方工具。但随着新版本的Webpack支持asset moduleurl-loader已经被官方标记为废弃了。

基于这一个技术的发展,原有的svg-mixed-loader也不再能独立地发展。我在这个issue中和Webpack官方进行了一系列的探讨,最终官方的意见是比较直白的:

不要让loader去做这么复杂的事,应该在规则上区分它们,并用资源查询来处理。

基于官方给出的意见,reSKRipt也计划做出相应的调整。

调整后的使用方式

如果你需要一个SVG为你提供URL字符串,则与原有保持一样。:

import url from './close.svg';

如果你希望获得一个组件,需要调整代码:

import IconClose from './close.svg?react';

注意在最后增加了一个?react的查询。

如果你两者都要,那么你需要写2条import语句。

在CSS中使用

在之前的版本中,如果你在CSS中引入一个SVG文件,它大概率是不能工作的:

.root {
background-image: url("./background.svg");
}

因为SVG被处理成了一个具备URL和组件代码的混合内容。为了保持向后兼容性,我们现在无法修改这个行为,但增加了一个方式让CSS可以直接引用图片:

.root {
background-image: url("./background.svg?asset");
}

你需要在资源上增加一个?asset的查询,即告知reSKRipt你只需要它被转化为一个URL。

废弃警告

如果你现在依然使用原来的ReactComponent导出,你的代码将正常工作,但在浏览器的控制台中将看到一条类似的警告:

import {ReactComponent} from '*.svg' is deprecated, please use import ReactComponent from '*.svg?react' instead.
This warning emits because you imported {ReactComponent} from /path/to/filename.svg

这条警告仅在development模式下出现,你可以快速定位到相关的代码并进行修改。

关于类型

当然,现在的方式使得.svg文件随着查询的不同会呈现出不同的类型,因此我们的TypeScript定义也要相应调整:

declare module '*.svg' {
const url: string;
export default url;
}

declare module '*.svg?react' {
import {SVGAttributes, ComponentType, RefAttributes} from 'react';

const Component: ComponentType<SVGAttributes<SVGElement> & RefAttributes<SVGElement>>;
export default Component;
}

以上代码我们建议放到src/types/static.d.ts中。