如何构建模块
本章讲解在可复用模块项目中编译构建的相关内容,相关 API 详见:
构建支持
首先简单介绍一下模块工程方案的构建支持。
模块类型支持
模块工程方案支持了丰富的模块类型,常见的模块包括
- ECMAScript Module
- CommonJS Module
- 资源模块
通过插件系统可以自由的扩展模块,可以将所有的文件以模块的形式集成进来。通过插件的方式,已经内置支持了更多的模块类型,其中包括:
产物格式支持
模块工程方案支持目前主流的模块系统 —— ESM (ECMA Script Module) 和 CJS (CommonJS)。同时还支持 UMD(Universal Module Definition)。
目标环境支持
模块工程方案默认支持所有最新的 JS 特性,即 esnext
。同时也可以通过 buildConfig.target
设置以支持其他目标环境。
类型文件支持
模块工程方案里内置了对于 TypeScript 的支持,同时支持生成类型声明文件。你可以通过 buildConfig.enableDts
和 buildConfig.dtsOnly
控制类型声明文件如何生成。
接下来具体学习一下如何使用模块工程提供的这些功能。
使用构建预设
当面对不同的通用构建场景(需求)的时候,可以通过使用 output.buildPreset
设置项目的构建配置。
output.buildPreset
是模块工程方案提供的构建预设配置。支持的预设值所对应的构建产物结果,是从社区里众多 NPM 包的产物结果中总结出来的,可以满足大部分场景下的构建需要。关于更多可用的预设值,可以查看 output.buildPreset
的文档。
模块工程方案提供的预设值也会不断的修改和新增。欢迎通过提 Issue 的形式,一起来补充和完善更多常用的构建场景预设。
使用自定义通用配置
当 output.buildPreset
配置无法满足构建需求的时候,推荐使用 output.buildConfig
进行自定义配置。output.buildConfig
是 Module Tools 构建功能的底层配置,output.buildPreset
也是基于 output.buildConfig
实现的。
下面讲解一下关于 output.buildConfig
的配置以及如何使用。首先需要认识一下【构建类型】。
构建类型
在模块工程方案里,支持 Bundle 和 Bundleless 两种形式来构建你的模块。
所谓 Bundle 构建是指将构建产物进行打包,但是未必一定打包成一个文件;而 Bundleless 构建指不对构建产物进行打包,每个构建产物文件都可以找到对应的源文件。
在开始执行构建之前,首先考虑项目的构建产物是否需要打包?针对打包或者不打包的情况,你需要对 buildConfig.buildType
分别配置 bundle
或者 bundleless
。
import { defineConfig } from '@modern-js/module-tools';
export default defineConfig({
output: {
buildConfig: {
buildType: 'bundle',
},
},
});
import { defineConfig } from '@modern-js/module-tools';
export default defineConfig({
output: {
buildConfig: {
buildType: 'bundleless',
},
},
});
如果项目包含多套构建产物,那么可以配置 output.buildConfig
为一个数组。output.buildConfig
不仅支持对象形式,同时也支持数组形式,可以同时输出多种构建产物。
“多套构建产物”是指支持不同的语法、模块化格式等条件的构建产物。
import { defineConfig } from '@modern-js/module-tools';
export default defineConfig({
output: {
buildConfig: [
{
buildType: 'bundle',
},
{
buildType: 'bundleless',
},
],
},
});
当确定好构建类型后,接下来就要考虑以下几件事:
- 构建产物的模块化格式是什么?
- 构建产物支持 ECMAScript 的最高版本是多少?
- 是否需要
d.ts
文件? - 是否需要
sourceMap
以及sourceMap
的使用形式? - 产物输出到哪里?
接下来就讲一下如何通过 output.buildConfig
进行这些配置。
选择模块化格式
模块工程方案在 Bundle 场景下支持 cjs
、esm
、umd
的产物格式以及在 Bundleless 场景下支持 cjs
和 esm
的产物格式。
当配置产物的模块化格式的时候,可以通过 buildConfig.format
进行配置。
例如当配置 Bundle 产物为 umd
格式的时候,可以按照如下进行配置:
import { defineConfig } from '@modern-js/module-tools';
export default defineConfig({
output: {
buildConfig: [
{
buildType: 'bundle',
format: 'umd',
},
],
},
});
基于上面的配置,增加构建 esm
格式的 Bundleless 构建产物的时候,配置如下:
import { defineConfig } from '@modern-js/module-tools';
export default defineConfig({
output: {
buildConfig: [
{
buildType: 'bundle',
format: 'umd',
},
{
buildType: 'bundleless',
format: 'esm',
},
],
},
});
确定支持的 ECMAScript 最高版本
配置构建产物支持的 ECMAScript 最高版本,或者说支持的目标环境,可以通过 buildConfig.target
进行配置。
ECMAScript 是一种由 Ecma 国际(前身为欧洲计算机制造商协会)在标准 ECMA-262 中定义的脚本语言规范。这种语言在万维网上应用广泛,它往往被称为 JavaScript 或 JScript,但实际上后两者是 ECMA-262 标准的实现和扩展。
当前模块工程方案支持以下 ECMAScript 版本:
es5
es6
es2015
es2016
es2017
es2018
es2019
es2020
esnext
可以按照如下方式进行配置:
import { defineConfig } from '@modern-js/module-tools';
export default defineConfig({
output: {
buildConfig: [
{
buildType: 'bundle',
format: 'umd',
target: 'es5',
},
{
buildType: 'bundleless',
format: 'esm',
target: 'es6',
},
],
},
});
注意
当前在 Bundleless 构建的情况下 buildConfig.target
仅支持 es5
和 es6
,后续会增加其他版本的支持。
生成 .d.ts
文件
当项目是一个 TypeScript 项目的时候,可以选择构建产物是否包含 d.ts
类型文件。通过 buildConfig.enableDts
配置选择是否开启 d.ts
文件生成以及类型检查。
默认情况下,该配置为 false
,即不生成 d.ts
文件,也不做类型检查。可以通过如下配置开启该功能:
import { defineConfig } from '@modern-js/module-tools';
export default defineConfig({
output: {
buildConfig: [
{
buildType: 'bundleless',
format: 'esm',
target: 'es6',
enableDts: true,
},
],
},
});
此时生成的 d.ts
文件会与其他产物在同一个目录下。
├── dist
│ ├── index.d.ts
│ └── index.js
当需要将 d.ts
文件输出到指定路径的时候,可以为 d.ts
文件的生成单独提供一份构建配置,同时需要开启 buildConfig.dtsOnly
配置。比如下面的配置表示仅生成 d.ts
文件并输出到 ./dist/types
目录下:
构建产物的根目录由
output.path
决定,默认为./dist
。
关于
buildConfig.outputPath
的使用,可参考 【产物的输出位置】。
├── dist
│ ├── types
│ │ └── index.d.ts
│ └── index.js
import { defineConfig } from '@modern-js/module-tools';
export default defineConfig({
output: {
buildConfig: [
{
enableDts: true,
dtsOnly: true,
outputPath: './types',
},
{
buildType: 'bundleless',
format: 'esm',
target: 'es6',
},
],
},
});
buildConfig.dtsOnly
配置常用在这个场景下。在非 TypeScript 项目下,该配置以及 buildConfig.enableDts
都不再生效。
除了上述讲到的内容以外,.d.ts
文件还支持 Bundle 和 Bundleless 两种生成模式。Bundleless 模式的 d.ts
文件生成与 TypeScript 一样,每个 d.ts
文件与源码文件一一对应;而 Bundle 模式的 d.ts
文件生成则会将所有类型文件进行打包处理,最终输出一个 d.ts
类型文件。
import { defineConfig } from '@modern-js/module-tools';
export default defineConfig({
output: {
buildConfig: [
{
buildType: 'bundle',
enableDts: true,
dtsOnly: true,
outputPath: './types',
},
],
},
});
import { defineConfig } from '@modern-js/module-tools';
export default defineConfig({
output: {
buildConfig: [
{
buildType: 'bundleless',
enableDts: true,
dtsOnly: true,
outputPath: './types',
},
],
},
});
最后,还可以指定生成 d.ts
类型文件所需要的 TSConfig 文件。通过 buildConfig.tsconfig
配置可以指定自定义的 TSConfig 文件名称。
import { defineConfig } from '@modern-js/module-tools';
export default defineConfig({
output: {
buildConfig: [
{
buildType: 'bundleless',
enableDts: true,
dtsOnly: true,
outputPath: './types',
tsconfig: './tsconfig.build.json',
},
],
},
});
处理 sourceMap
默认情况下,模块工程方案在 Bundle 构建的产物中提供 SourceMap 文件,而在 Bundleless 构建的产物中不会提供。
如果需要进行自定义配置,则可以配置 buildConfig.sourceMap
。
import { defineConfig } from '@modern-js/module-tools';
export default defineConfig({
output: {
buildConfig: [
{
buildType: 'bundleless',
sourceMap: true,
},
],
},
});
产物的输出位置
当存在多个构建配置的时候,往往可能需要考虑为每个构建配置提供对应的产物输出路径。
模块工程方案提供了 buildConfig.outputPath
进行配置。
例如当产物的根目录为 ./lib
,将 esm
产物输出到 ./lib/esm
目录下,将 cjs
产物输出到 ./lib/cjs
目录下,则可以按照以下方式进行配置:
import { defineConfig } from '@modern-js/module-tools';
export default defineConfig({
output: {
path: './lib',
buildConfig: [
{
format: 'esm',
outputPath: './esm',
},
{
format: 'cjs',
outputPath: './cjs',
},
],
},
});
以上所有的配置都是 Bundle 和 Bundleless 构建的通用配置,可以满足大部分需求。不过 Bundle 和 Bundleless 构建之间也有一些差异,对于这些差异就需要通过不同的 API 进行区分。模块工程方案针对于这种情况提供了 buildConfig.bundleOptions
和 buildConfig.bundlelessOptions
API 分别对 Bundle 和 Bundleless 构建进行自定义配置。
使用自定义 Bundle(less) 配置
Bundle 构建
对于 Bundle 这种场景,模块工程方案使用 Speedy 作为 Bundler。对于 Speedy,你可以理解为它是一个 Universal Bundler。它使用 Tapable 扩展了 esbuild 的插件系统,带来了极致的编译速度和友好的插件系统,可以满足 Bundle 的构建需求。
当使用 Bundle 构建的时候,一般情况下,你无需任何 Bundle 相关的配置,只需要将 buildConfig.buildType
设置为 'bundle'
即可。
import { defineConfig } from '@modern-js/module-tools';
export default defineConfig({
output: {
buildConfig: [
{
buildType: 'bundle',
},
],
},
});
这里之所以不需要任何配置,是因为 Bundler 会默认从入口 src/index.(j|t)s
开始,收集模块的依赖,生成一份模块依赖图,再将其转换成一份 Chunk 图并把文件写入磁盘中。
Bundle 相比于 Bundleless 场景,是需要提供构建的入口(文件)。当 Bundle 构建的入口文件不是 src/index.(j|t)s
的时候,就需要通过手动配置 bundleOptions.entry
来修改。
例如打包一个组件项目,入口文件为 src/index.tsx
,则可以按照如下方式进行配置:
import { defineConfig } from '@modern-js/module-tools';
export default defineConfig({
output: {
buildConfig: [
{
buildType: 'bundle',
bundleOptions: {
entry: {
'index': './src/index.tsx',
},
},
},
],
},
});
默认情况下,Bundle 构建只会将源码文件打包成一个 Chunk。这是因为模块工程方案提供的 bundleOptions.skipDeps
默认值为 true
。bundleOptions.skipDeps
用于跳过项目里 package.json
上所有的依赖打包进产物中。如果不希望使用默认配置,则可以通过下面的方式关闭:
import { defineConfig } from '@modern-js/module-tools';
export default defineConfig({
output: {
buildConfig: [
{
buildType: 'bundle',
bundleOptions: {
entry: {
'index': './src/index.tsx',
},
skipDeps: false,
},
},
],
},
});
bundleOptions.skipDeps
功能是基于 bundleOptions.externals
实现的。当项目需要自定义外置某些依赖的时候,例如外置 React 依赖,可以按照如下方式进行配置:
import { defineConfig } from '@modern-js/module-tools';
export default defineConfig({
output: {
buildConfig: [
{
buildType: 'bundle',
bundleOptions: {
entry: {
'index': './src/index.tsx',
},
skipDeps: false, // 必须要配置
externals: ['React'],
},
},
],
},
});
除了 bundleOptions.skipDeps
和 bundleOptions.externals
这两个配置用于控制如何打包外部的依赖模块,模块工程方案还提供了 bundleOptions.platform
用于设置构建环境。
除了上述这些功能,Bundler 也提供了代码分割和代码压缩这样的优化功能。默认情况下,它们是关闭的。你可以通过阅读它们的 API 文档了解如何使用。
Bundleless 构建
对于 Bundleless 这种场景,模块工程方案使用 Babel 作为 Bundleless 的 JavaScript 和 TypeScript 编译工具。
当使用 Bundleless 构建的时候,一般情况下,无需任何 Bundleless 相关的配置,只需要将 buildConfig.buildType
设置为 'bundleless'
即可。
import { defineConfig } from '@modern-js/module-tools';
export default defineConfig({
output: {
buildConfig: [
{
buildType: 'bundleless',
},
],
},
});
这里之所以不需要任何配置,是因为模块工程方案对于 Bundleless 构建会默认将项目的 src/
目录作为源码目录,对这个目录下的所有文件进行编译。
当需要修改源码目录的时候,可以通过手动配置 bundlelessOptions.sourceDir
来实现。例如指定 lib/
目录为源码目录,可以按照如下方式进行配置:
import { defineConfig } from '@modern-js/module-tools';
export default defineConfig({
output: {
buildConfig: [
{
buildType: 'bundleless',
bundlelessOptions: {
sourceDir: './lib',
}
},
],
},
});
模块工程方案针对样式的编译处理,提供了基于 Less、Sass、PostCSS 实现的样式“编译器”。同时提供了 bundlelessOptions.style.compileMode
API 来决定使用样式“编译器”的方式,下面详细讲解如何使用这个配置。
对于包含了样式文件的 Bundleless 构建,默认情况下,产物中会包含编译后的样式文件(CSS 文件)以及样式源码文件。因为此时 bundlelessOptions.style.compileMode
的默认值为 'all'
,即编译样式源码成为产物的同时也会复制一份样式源码到产物中。
当希望项目中仅包含样式编译后的产物的时候,则可以通过以下方式进行配置:
import { defineConfig } from '@modern-js/module-tools';
export default defineConfig({
output: {
buildConfig: [
{
buildType: 'bundleless',
bundlelessOptions: {
style: {
compileMode: 'only-compiled-code',
},
},
},
],
},
});
而如果希望项目里的样式文件交给其他地方来处理,即产物中仅保留样式源码,则可以通过以下方式进行配置:
import { defineConfig } from '@modern-js/module-tools';
export default defineConfig({
output: {
buildConfig: [
{
buildType: 'bundleless',
bundlelessOptions: {
style: {
compileMode: 'only-source-code',
},
},
},
],
},
});
最后对于一些特殊情况,希望不开启对样式的任何编译和处理,则可以通过以下方式关闭:
import { defineConfig } from '@modern-js/module-tools';
export default defineConfig({
output: {
buildConfig: [
{
buildType: 'bundleless',
bundlelessOptions: {
style: {
compileMode: false,
},
},
},
],
},
});
当确定如何处理样式文件后,模块工程方案还为样式产物提供了自定义指定输出位置的配置,可以通过 bundlelessOptions.style.path
来是实现:
import { defineConfig } from '@modern-js/module-tools';
export default defineConfig({
output: {
buildConfig: [
{
buildType: 'bundleless',
bundlelessOptions: {
style: {
compileMode: false,
path: './styles',
},
},
},
],
},
});
上面的例子表示:将样式的产物输出到 /dist/styles
目录下面。
关于上面例子的详解
默认情况下,output.path
为 /dist
,buildConfig.outputPath
为 ./
,bundlelessOptions.style.path
为 ./styles
。因此最终样式产物输出的路径为 ./dist/styles
。
同样的,模块工程方案对 Bundleless 构建的静态文件输出目录也提供了类似的 API,可以通过 bundlelessOptions.static.path
进行配置,例如将所有的静态文件输出到 dist/static/
目录下:
import { defineConfig } from '@modern-js/module-tools';
export default defineConfig({
output: {
buildConfig: [
{
buildType: 'bundleless',
bundlelessOptions: {
static: {
path: './static',
},
},
},
],
},
});
对于 bundlelessOptions.style.path
和 bundlelessOptions.static.path
,当对它们进行配置的时候,也会影响 Babel 编译的代码中对使用到的样式文件路径和静态资源文件路径的处理。模块工程方案内部提供了支持这两个 API 的 Babel 插件。
修改编译工具配置
当【构建预设】、【自定义通用配置】、【自定义 Bundle(less) 配置】不能满足需求,并且希望修改关于 Babel 或者 Speedy 配置的时候,可以通过以下 API 进行配置: