在没有构建系统的情况下编写JavaScript
当我开始一个新的前端项目时,我总是面临一个问题:是否应该使用构建系统?今天,我想谈谈构建系统对我的吸引力,以及为什么我(通常)仍然不使用它们,以及为什么我觉得有些前端JavaScript库要求你使用构建系统令人沮丧。
我写这篇文章是因为我看到大多数关于JS的文章都假设你使用构建系统,对于像我这样编写非常简单的小型JavaScript项目的人来说,导航可能会有些困难,而这些项目不需要构建系统。
什么是构建系统?
构建系统的概念是,你有一堆JavaScript或TypeScript代码,希望在将其放在网站上之前将其转换为不同的JavaScript代码。
构建系统可以执行许多有用的任务,比如:
- 将100多个JS文件合并为一个大的捆绑包(出于效率原因)
- 将TypeScript翻译成JavaScript
- 对TypeScript进行类型检查
- 最小化
- 添加多兼容性支持旧浏览器的polyfills
- 编译JSX
- 剪枝(删除未使用的JS代码以减小文件大小)
- 构建CSS(例如,Tailwind所做的)
- 还有可能的其他重要任务
因此,如果你今天正在构建一个复杂的前端项目,你可能正在使用像webpack、rollup、esbuild、parcel或vite这样的构建系统。
目标:轻松更改旧的小型网站
我制作了许多小型、简单的网站,我几乎没有为它们中的任何一个提供维护能力,并且我很少更改它们。
我的目标是,如果我有一个在3年或5年前制作的站点,我希望在20分钟内能够:
- 在新计算机上从GitHub获取源代码
- 进行一些更改
- 将其上传到网上
但是,根据我的经验,如果使用构建系统(不仅仅是JavaScript构建系统!),如果你有一个5年前的站点,要使站点重新构建通常会非常麻烦。
而且由于我的大多数网站都很小,使用构建系统的优势相对较小——我不需要TypeScript或JSX。我只需有一个400行的script.js
文件,就能搞定。
示例:尝试构建SQL游乐场
我有一个网站(SQL游乐场),它使用构建系统(使用Vue)。我上次编辑该项目是2年前,而且是在不同的计算机上。
让我们看看我今天是否还能轻松在我的计算机上构建它。首先,我们必须运行npm install
。下面是我得到的输出。
$ npm install
[很多输出已省略]
npm ERR! code 1
npm ERR! path /Users/bork/work/sql-playground.wizardzines.com/node_modules/grpc
npm ERR! command failed
npm ERR! command sh /var/folders/3z/g3qrs9s96mg6r4dmzryjn3mm0000gn/T/install-b52c96ad.sh
npm ERR! CXX(target) Release/obj.target/grpc/deps/grpc/src/core/lib/surface/init.o
npm ERR! CXX(target) Release/obj.target/grpc/deps/grpc/src/core/lib/avl/avl.o
npm ERR! CXX(target) Release/obj.target/grpc/deps/grpc/src/core/lib/backoff/backoff.o
npm ERR! CXX(target) Release/obj.target/grpc/deps/grpc/src/core/lib/channel/channel_args.o
npm ERR! CXX(target) Release/obj.target/grpc/deps/grpc/src/core/lib/channel/channel_stack.o
npm ERR! CXX(target) Release/obj.target/grpc/deps/grpc/src/core/lib/channel/channel_stack_builder.o
npm ERR! CXX(target) Release/obj/target/grpc/deps/grpc/src/core/lib/channel/channel_trace.o
npm ERR! CXX(target) Release/obj/target/grpc/deps/grpc/src/core/lib/channel/channelz.o
出现了构建grpc
的错误。没问题,我实际上并不需要那个依赖项,所以我只需花5分钟将它删除并重新构建。现在,我可以运行npm install
,一切正常。
现在让我们尝试构建该项目:
$ npm run build
? Building for production...Error: error:0308010C:digital envelope routines::unsupported
at new Hash (node:internal/crypto/hash:71:19)
at Object.createHash (node:crypto:130:10)
at module.exports (/Users/bork/work/sql-playground.wizardzines.com/node_modules/webpack/lib/util/createHash.js:135:53)
at NormalModule._initBuildHash (/Users/bork/work/sql-playground.wizardzines.com/node_modules/webpack/lib/NormalModule.js:414:16)
at handleParseError (/Users/bork/work/sql-playground.wizardzines.com/node_modules/webpack/lib/NormalModule.js:467:10)
at /Users/bork/work/sql-playground.wizardzines.com/node_modules/webpack/lib/NormalModule.js:499:5
at /Users/bork/work/sql-playground.wizardzines.com/node_modules/webpack/lib/NormalModule.js:356:12
at /Users/bork/work/sql-playground.wizardzines.com/node_modules/loader-runner/lib/LoaderRunner.js:373:3
at iterateNormalLoaders
(/Users/bork/work/sql-playground.wizardzines.com/node_modules/loader-runner/lib/LoaderRunner.js:214:10)
at iterateNormalLoaders (/Users/bork/work/sql-playground.wizardzines.com/node_modules/loader-runner/lib/LoaderRunner.js:221:10)
at /Users/bork/work/sql-playground.wizardzines.com/node_modules/loader-runner/lib/LoaderRunner.js:236:3
at runSyncOrAsync (/Users/bork/work/sql-playground.wizardzines.com/node_modules/loader-runner/lib/LoaderRunner.js:130:11)
at iterateNormalLoaders (/Users/bork/work/sql-playground.wizardzines.com/node_modules/loader-runner/lib/LoaderRunner.js:232:2)
at Array.<anonymous> (/Users/bork/work/sql-playground.wizardzines.com/node_modules/loader-runner/lib/LoaderRunner.js:205:4)
at Storage.finished (/Users/bork/work/sql-playground.wizardzines.com/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js:43:16)
at /Users/bork/work/sql-playground.wizardzines.com/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js:79:9
这里出现了一个错误,显示“error:0308010C:digital envelope routines::unsupported”。这个堆栈溢出答案建议运行export NODE_OPTIONS=--openssl-legacy-provider
来修复此错误。
这个方法有效,最后我可以运行npm run build
来构建项目。
这并不算太糟糕(我只需要删除一个依赖项并传递一个稍微神秘的节点选项!),但我宁愿不被这些构建错误拖住。
对我来说,对于小项目,构建系统通常不值得
对于我来说,复杂的JavaScript构建系统对于小型500行的项目来说通常并不值得——这意味着为了一些较小的好处而放弃了未来轻松更新项目的能力。
esbuild似乎更加稳定
我想快速提一下esbuild:我在2021年了解了esbuild,并在一个项目中使用了它,到目前为止,它似乎是构建JS项目的更可靠方式。
我刚刚尝试在新计算机上构建一个8个月前上次触碰的esbuild
项目,它可以工作。但我不能确定在2年后我是否仍能轻松构建该项目。也许会,我希望如此!
通常情况下,不使用构建系统通常很容易
以下是导入所有库的nginx playground代码的部分示例:
<script src="js/vue.global.prod.js"></script>
<script src="codemirror-5.63.0/lib/codemirror.js"></script>
<script src="codemirror-5.63.0/mode/nginx/nginx.js"></script>
<script src="codemirror-5.63.0/mode/shell/shell.js"></script>
<script src="codemirror-5.63.0/mode/javascript/javascript.js"></script>
<link rel="stylesheet" href="codemirror-5.63.0/lib/codemirror.css">
<script src="script.js "></script>
这个项目也使用Vue,但它只使用<script src>
来加载Vue——前端没有构建过程。
使用Vue的无构建系统模板
一些人询问如何开始编写没有构建系统的JavaScript。当然,如果你愿意,可以编写纯JS,但我的常用框架是Vue 3。
这是一个我创建的微型模板,用于启动不使用构建系统的Vue 3项目。它只有2个文件和约30行HTML/JS。
一些库要求你使用构建系统
最近我在使用CodeMirror 5进行新项目时考虑到构建系统的问题,并且我看到有一个新版本CodeMirror 6。
因此,我认为——很酷,也许我应该使用CodeMirror 6而不是CodeMirror 5。但是——看起来你不能在没有构建系统的情况下使用CodeMirror 6(根据迁移指南)。因此,我将继续使用CodeMirror 5。
类似地,你以前可以只下载Tailwind作为一个巨大的CSS文件,但是Tailwind 3似乎不再作为一个大的CSS文件提供,你需要运行JavaScript来构建它。因此,我现在将继续使用Tailwind 2。(我知道,我知道,你不应该使用大的CSS文件,但它只有300KB经过gzip压缩,而且我真的不想要构建步骤)
(编辑:看起来Tailwind在2021年发布了一个独立的CLI,这似乎是一个不错的选择)
我不太确定为什么有些库不提供无构建系统版本——也许提供无构建系统版本会给库增加很多额外的复杂性,而维护者认为这不值得。或者也许库的设计意味着由于某种原因不可能分发无构建系统版本。
我想要更多关于无构建系统JavaScript的提示
到目前为止,我的主要策略包括:
- 在库的网站上搜索“CDN”以查找独立的JavaScript文件
- 使用unpkg.com查看是否可以使用构建版本的库
- 托管自己的库版本,而不依赖可能会崩溃的CDN
- 编写自己的简单集成,而不是引入其他
- 如果需要用构建工具,那就用esbuild