kpack
v0.0.23
Published
kpack is a web build tool
Downloads
8
Readme
kpack
kpack是一个追求更好构建产物的前端构建工具。它基于rollup,并兼容vite,如何使用基本可参考vite。是一个面向Web的rollup实践。
目录
为什么开发kpack
为什么不使用Webpack
业界很多人对Webpack的速度不满意,但是我主要是对Webpack的构建产物不满意。首先Webpack的运行时太重了。还有就是Webpack对如何加载模块,没有开放太多的能力。我希望开发一次,打包时可以用不同的配置方案打多个包,然后在HTML中可以根据浏览器兼容性进行条件引入。Webpack对于polyfill的使用也不能让人满意。Webpack是将polyfill作为普通的依赖库一样,由webpack-runtime管理,这导致webpack-runtime自身所需的polyfills无法提前引入。在我的理念里,我把polyfills分成2部分。运行时必要的polyfills和运行时在一起在最前面引入;其余polyfills按普通依赖处理。在一番探索后我决定使用rollup这个工具。
为什么不使用Vite
在使用了一段时间rollup后,Vite出现了。Vite的设计很符合我的胃口,no bundle的设计大大提升了开发服务器的速度;打包时按照浏览器打不同的包也正是我想要的。但是Vite有着许多问题,让我只好继续使用我自己配置的打包方案。Vite大规模地使用了esbuild,让esbuild处理commonjs是没问题的,但是esbuild处理TS和CSS的能力很弱,很多特殊语法不支持,我这里使用了被广泛使用的Babel和PostCSS。Vite对通过路径使用CSS没有很好的方案。vite-legacy对兼容性打包不够理想,多条件兼容性的打包只处理了Babel和polyfills,像CSS、图片、别名,无法分别处理。多条件打包只分了2个版本,没能分得更细。经常出现一些polyfills没有被自动注入的情况。Vite把所有polyfills汇聚到一起,放到页面最前面运行。这会导致首屏内容增加。
开始
快速创建项目
在当前路径创建新项目
npm init kpack@latest
在指定路径创建新项目
npm create kpack@latest some-directory
手动安装依赖
手动用 NPM 安装 kpack 到工程中。
npm i -D kpack
配置
在项目根目录中创建一个名为 kpack.config.js 的文件,并添加以下代码:
const { defineConfig } = require("kpack");
module.exports = function({ command, mode }) {
return defineConfig({
//这里是配置内容
});
};
其中配置内容根据后续需要进行配置
运行
在安装了 kpack 的项目中,可以在 package.json
的 scripts
中使用 kpack 命令行运行它。下面是一个典型的 npm scripts 例子:
{
"scripts": {
"dev": "kpack serve", // 启动开发服务器
"build": "kpack build", // 构建产物
"preview": "kpack preview" // 本地预览构建产物
}
}
然后使用命令 npm run build
进行构建。
功能特性
NPM依赖
kpack可以导入npm上的模块。在原生 ES 导入不支持下面这样的裸模块导入:
import { useState } from 'react'
上面的代码会在浏览器中抛出一个错误。而在 kpack 中,可以导入npm中已安装的库。
TypeScript
kpack 内置了 ts
、tsx
的支持。可以通过创建tsconfig.json,进行ts配置。
tslib
内置ts使用babel实现,因此不会依赖tslib,与其他第三方库一样,经过babel转译后最终依赖 @babel/runtime
。转译过程只执行 ts 的转译工作,并不执行任何类型检查。内部对每个ts文件独立转化,因此需要配置 isolatedModules
为 true。
useDefineForClassFields
useDefineForClassFields
默认值为 true。
experimentalDecorators
experimentalDecorators
默认值为 false。 即按照stage3的方式转译装饰器。当 experimentalDecorators
设为 true 时,按照ts中 experimentalDecorators
的方式进行转译(并非babel的legacy模式)。
使用Typescript插件
您可以使用 @rollup/plugin-typescript
插件来达到与官方typescript一致的效果。
JSX
.jsx
也内置支持了。可以通过配置的jsx选项配置开启。.tsx
的支持见 TypeScript。
{
jsx?: {
runtime: 'classic' | 'automatic';
pragma?: string;
pragmaFrag?: string;
importSource?: string;
};
}
静态资源路径引入
导入一个静态资源会返回解析后的 URL:
import imgUrl from "./img.png"
document.getElementById('hero-img').src = imgUrl;
也可以按以下方式编写
document.getElementById('hero-img').src = new URL('./img.png', import.meta.url).href;
CSS
导入 .css 文件,会在加载js时把css内容,用link标签动态引入。也能够以路径的形式引入 CSS。
import './example.css';
let link = document.createElement("LINK");
link.rel = "stylesheet";
link.type = "text/css";
// 作为路径使用
link.href = new URL('./example.css', import.meta.url).href;
@import 内联和变基
css内部如果使用了@import url(xxxx.css)
,将会以内联的方式引入另一个CSS。如果,被引入的css含有相对路径,那么这个相对路径是相对于被引入的CSS的。
替换是基于语法解析的,因此在字符串或是注释中的变量不会被替换。
PostCSS
如果项目包含有效的 PostCSS 配置 (任何受 postcss-load-config 支持的格式,例如 postcss.config.js
),它将会自动应用于所有已导入的 CSS。
CSS预处理器
kpack内置了对 .scss
, .sass
的支持。没有必要为它们安装特定的插件,但必须安装相应的预处理器依赖
# .scss and .sass
npm add -D sass
CSS Modules
任何以 .module.css 为后缀名的 CSS 文件都被认为是一个 CSS modules 文件。导入这样的文件会返回一个相应的模块对象:
/* example.module.css */
.red {
color: red;
}
import classes from './example.module.css'
document.getElementById('foo').className = classes.red
CSS modules 行为可以通过 css.modules 选项 进行配置。
你还可以和预处理器配合使用,例如 style.module.scss
JSON
JSON 可以被直接导入 —— 同样支持具名导入:
import json from './example.json'
动态导入
动态导入可以通过模板字符串得到有限支持。
const module = await import(`./dir/${file}.js`)
注意变量仅代表一层深的文件名。如果 file 是 foo/bar,导入将会失败。对于更进阶的使用详情,你可以使用 glob 导入 功能。
Glob导入
kpack 支持使用特殊的 import.meta.glob
函数从文件系统导入多个模块:
const modules = import.meta.glob('./dir/*.js')
以上将会被转译为下面的样子:
const modules = {
'./dir/foo.js': () => import('./dir/foo.js'),
'./dir/bar.js': () => import('./dir/bar.js'),
}
你可以遍历 modules 对象的 key 值来访问相应的模块:
for (const path in modules) {
modules[path]().then((mod) => {
console.log(path, mod)
})
}
匹配到的文件默认是懒加载的,通过动态导入实现,并会在构建时分离为独立的 chunk。如果你倾向于直接引入所有的模块(例如依赖于这些模块中的副作用首先被应用),你可以使用 import.meta.globEager
代替:
const modules = import.meta.globEager('./dir/*.js')
以上会被转译为下面的样子:
import * as __glob__0_0 from './dir/foo.js'
import * as __glob__0_1 from './dir/bar.js'
const modules = {
'./dir/foo.js': __glob__0_0,
'./dir/bar.js': __glob__0_1,
}
环境变量和模式
环境变量
kpack 在一个特殊的 import.meta.env
上暴露环境变量。这里提供了一些内建变量:
import.meta.env.MODE
: {string} 应用运行的模式。import.meta.env.BASE_URL
: {string} 构建资源地址前缀。他由base配置项决定。import.meta.env.DEV
: {boolean} 应用是否运行在开发环境 (判断 NODE_ENV == 'development')。import.meta.env.PROD
: {boolean} 应用是否运行在生产环境 (永远与 import.meta.env.DEV相反)。
静态替换
这些环境变量会被静态替换,因此,在引用它们时请使用完全静态的字符串。动态的 key 将无法生效。例如,动态 key 取值 import.meta.env[key] 是无效的。
替换是基于语法解析的,因此在字符串或是注释中的变量不会被替换。
.env 文件
kpack 使用 dotenv 从你的 环境目录 中的下列文件加载额外的环境变量:
.env # 所有情况下都会加载
.env.local # 所有情况下都会加载,但会被 git 忽略
.env.[mode] # 只在指定模式下加载
.env.[mode].local # 只在指定模式下加载,但会被 git 忽略
加载的环境变量在构建代码中可以可以使用 process.env 获取;在客户端代码通过 import.meta.env 获取。
为了防止意外地将一些环境变量泄漏到客户端,通过envPrefix可以配置以哪些前缀可以暴露。
{
"envPrefix": ["VITE_", "VUE_APP_"]
}
模式
- development 开发
- staging 测试
- production 生产
你可以通过传递 --mode 选项标志来使用模式。例如,如果你想为我们假设的 staging 模式构建应用:
kpack build --mode staging
而后,会从 .env.staging
文件中加载配置。
构建优化
CSS 代码分割
kpack 会自动地将一个异步 chunk 模块中使用到的 CSS 代码抽取出来并为其生成一个单独的文件。
资源预加载
在使用“dynamic import”加载脚本时,会同时预加载所依赖的js、css、img,从而避免界面“闪烁”的发生。
使用插件
kpack 可以使用插件进行扩展。可以兼容rollup插件,和vite插件。
添加一个插件
若要使用一个插件,需要将它添加到项目的 devDependencies 并在 kpack.config.js 配置文件中的 plugins 数组中引入它。例如,要想为esm提供require支持,可以按下面这样使用插件。
安装插件
npm add -D rollup-plugin-require
配置插件
const { defineConfig } = require("kpack");
const requireToImport = require("rollup-plugin-require");
module.exports = function({ command, mode }) {
return defineConfig({
plugins: [
requireToImport()
]
});
};
使用插件
document.getElementById("img").src = require("@/assets/image/foo.png");
强制插件排序
不同插件可能需要按一定执行顺序运行才能正确运行。可以使用 enforce 修饰符来强制插件的位置。enforce 有 "pre"
、"post"
、undefined
三个可选值。运行顺序如下:
- "pre":核心插件之前调用,一般用来处理自定义格式或语法。
- 核心插件:环境变量、文件处理、ts\jsx\json\css\commonjs
- undefined:核心插件之后调用,到此的都是正规JS语法。JS处理插件都可以在此运行。
- 输出插件:到此已经处理完了功能,这里是非功能插件进行补充,如兼容性处理。
- "post":用来处理自定义补充。
const { defineConfig } = require("kpack");
const xml = require("rollup-plugin-xml");
module.exports = function({ command, mode }) {
return defineConfig({
plugins: [
{
...xml(),
enforce: 'pre'
}
]
});
};
按需应用
默认情况下插件在开发 (serve) 和生产 (build) 模式中都会调用。如果插件在服务或构建期间按需使用,请使用 apply 属性指明它们仅在 'build' 或 'serve' 模式时调用:
常用插件
- rollup-plugin-import 按需打包
- rollup-plugin-require require转import插件
命令行
|选项|功能| |-|-| |-v, --version|输出软件版本| |-i, --info|输出软件信息| |-h, --help|输出命令帮助|
kpack -h
开发服务器
用法
kpack serve [root]
选项
|选项|功能|
|-|-|
|--host [host]
|限制访问请求头中的HOST|
|--port <port>
|监听端口|
|--open [path]
|运行后自动启动浏览器|
|--sourcemap
|是否输出源代码映射文件|
|-c, --config <file>
|配置文件路径|
|--base <path>
|资源路径引用前缀|
|--logLevel <level>
|日志输出等级info
/warn
/error
/silent
|
|-m, --mode <mode>
|模式,如development
/production
/staging
|
|-h, --help
|输出帮助|
构建
用法
kpack build [root]
选项
|选项|功能|
|-|-|
|--outDir <dir>
|输出路径 (默认值: dist)|
|--assetsDir <dir>
|输出路径中的资源路径|
|--sourcemap
|是否输出源代码映射文件|
|--minify
|是否压缩输出文件|
|-c, --config <file>
|配置文件路径|
|--base <path>
|资源路径引用前缀|
|--logLevel <level>
|日志输出等级info
/warn
/error
/silent
|
|-m, --mode <mode>
|模式,如development
/production
/staging
|
|-h, --help
|输出帮助|
预览
运行一个服务器来预览已构建的包
用法
kpack preview [root]
选项
|选项|功能|
|-|-|
|--host [host]
|限制访问请求头中的HOST|
|--port <port>
|监听端口|
|--open [path]
|运行后自动启动浏览器|
|--sourcemap
|是否输出源代码映射文件|
|-c, --config <file>
|配置文件路径|
|--base <path>
|资源路径引用前缀|
|--logLevel <level>
|日志输出等级info
/warn
/error
/silent
|
|-m, --mode <mode>
|模式,如development
/production
/staging
|
|-h, --help
|输出帮助|
配置项
运行 kpack 时, kpack 会自动解析 项目根目录 下名为 kpack.config.js 的文件。配置文件导出一个函数,由函数执行取得配置。
const { defineConfig } = require("kpack");
module.exports = function({ command, mode }) {
return defineConfig({
//这里是配置内容
});
};
这样设计的原因是这个函数会多次执行,会多次打包。
共享配置
root
- 类型:
string
- 默认:
process.cwd()
root 项目根目录
base
- 类型:
string
- 默认:
"/"
在HTML中,引入的js、css等资源的路径前缀。合法的值包括以下几种:
- 绝对 URL 路径名,例如
"/foo/"
- 完整的 URL,例如
"https://bar.com/foo/"
- 相对路径,例如
"./"
define
- 类型:
Record<string, string>
定义全局常量替换方式。使用这种方式配置的值不会进行语法解析而是直接替换。因此常量值需要用JSON.stringify转译。也可以传入js表达式。
module.exports = function({ command, mode }) {
return {
define: {
__APP_VERSION__: JSON.stringify('v1.0.0'),
__API_BASE__: 'location.origin',
}
};
};
plugins
- 类型:
(Plugin | Plugin[])[]
兼容rollup插件和vite插件。Falsy 虚值的插件将被忽略,插件数组将被扁平化(flatten)。
publicDir
- 类型:
string | false
- 默认:
"public"
作为静态资源服务的文件夹。该目录中的文件在开发期间在 /
处提供,并在构建期间复制到 outDir
的根目录,并且始终按原样提供或复制而无需进行转换。该值可以是文件系统的绝对路径,也可以是相对于项目根目录的相对路径。
将 publicDir
设定为 false
可以关闭此项功能。
resolve.alias
- 类型:
Record<string, string> | Array<{ find: string | RegExp, replacement: string, customResolver?: ResolverFunction | ResolverObject }>
将会被传递到 @rollup/plugin-alias
作为 entries 的选项。也可以是一个对象,或一个 { find, replacement, customResolver }
的数组。
resolve.dedupe
- 类型:
string[]
此选项强制将node包解析为项目根目录所依赖的node包。
resolve.extensions
- 类型:
string[]
- 默认:
['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx']
导入时想要省略的扩展名列表。注意,不 建议忽略自定义导入类型的扩展名(例如:.vue),因为它会影响 IDE 和类型支持。
css.modules
配置 CSS modules 的行为。选项将被传递给 postcss-modules。
css.postcss
内联的 PostCSS 配置(格式同 postcss.config.js)。
对内联的 POSTCSS 配置,它期望接收与 postcss.config.js 一致的格式。但对于 plugins 属性有些特别,只接收使用 数组格式。
搜索是使用 postcss-load-config 完成的,只有被支持的文件名才会被加载。
css.preprocessorOptions
指定传递给 CSS 预处理器的选项。文件扩展名用作选项的键。参考配置:
module.exports = function({ command, mode }) {
return {
css: {
preprocessorOptions: {
scss: {
additionalData: `$theme-primary: orange;`,
importer: require('node-sass-tilde-importer'),
},
sass: {
additionalData: `$theme-primary: orange;`,
importer: require('node-sass-tilde-importer'),
},
}
}
};
};
assetsInclude
- 类型:
string | RegExp | (string | RegExp)[]
- 相关内容: 静态资源路径引入
logLevel
- 类型:
'info' | 'warn' | 'error' | 'silent'
调整控制台输出的级别,默认为 'info'
。
envDir
- 类型:
string
- 默认: root
用于加载
.env
文件的目录。可以是一个绝对路径,也可以是相对于项目根的路径。
关于环境文件的更多信息,请参见 这里。
envPrefix
- 类型:
string | string[]
- 默认:
["APP_","VUE_APP_","REACT_APP_"]
以 envPrefix
开头的环境变量会通过 import.meta.env
暴露在你的客户端源码中。
开发服务器选项
server.host
- 类型:
string
- 默认:
undefined
指定服务器应该监听哪个 IP 地址。 如果将此设置为 undefined
将监听所有地址,包括局域网和公网地址。
也可以通过 CLI 使用 --host foo.bar.com。
server.port
- 类型:
number
- 默认值:
5173
指定开发服务器端口。注意:如果端口已经被使用,kpack 不会自动尝试下一个可用的端口,所以这需要保证端口没有被占用。
server.open
- 类型: boolean
- 开发服务器启动时,自动在浏览器中打开应用程序。
server.contextPath
- 类型:
boolean
- 默认值:
"/"
或 base
服务器二级目录,必须以"/"开头以"/"结尾。如果base的配置以"/"开头以"/"结尾,那么默认值为base。
module.exports = function({ command, mode }) {
return {
base: "./",
server: {
contextPath: "/my-app/"
}
};
};
server.proxy
- 类型:
Record<string, string | ProxyOptions>
为开发服务器配置自定义代理规则。期望接收一个 { key: options }
对象。任何请求路径以 key 值开头的请求将被代理到对应的目标。如果 key 值以 ^
开头,将被识别为 RegExp
。configure
选项可用于访问 proxy 实例。
其他配置项请参考 http-proxy。
在某些情况下,你可能也想要配置底层的开发服务器。(例如添加自定义的中间件到内部的 connect 应用中)为了实现这一点,你需要编写你自己的插件并使用 configureServer
函数。
构建配置
build.outDir
- 类型:
string
- 默认:
dist
指定输出路径(相对于项目根目录)。
build.assetsDir
- 类型:
string
- 默认:
assets
指定生成静态资源的存放路径(相对于 build.outDir)。
build.sourcemap
- 类型:
boolean
- 默认:
false
构建后是否生成 source map 文件。如果为 true,将会创建一个独立的 source map 文件。
build.minify
- 类型:
boolean
- 默认:
false
是否开启代码混淆。
build.reportCompressedSize
- 类型:
boolean
- 默认:
true
启用/禁用 gzip 压缩大小报告。压缩大型输出文件可能会很慢,因此禁用该功能可能会提高大型项目的构建性能。
build.chunkSizeWarningLimit
- 类型:
number
- 默认:
500
启用/禁用 gzip 压缩大小报告。压缩大型输出文件可能会很慢,因此禁用该功能可能会提高大型项目的构建性能。
规定触发警告的 chunk 大小。(以 kB 为单位)。它将与未压缩的 chunk 大小进行比较,因为 JavaScript 大小本身与执行时间相关。
预览配置
预览选项与开发服务器选项相同,请参考开发服务器选项配置。如果未配置,默认值取开发服务器选项中配置的值。
参考示例
module.exports = function({ command, mode }) {
return {
preview: {
host: "localhost",
port: 4173, // 默认值
contextPath: "/my-app/"
}
};
};
与X的区别是?
与vite的差异
字符串替换
在vite中有许多特性是直接字符串替换而非语法解析的,使用kpack总是进行语法解析。
const foo = 'I like import.meta.env.MODE';
.foo::after{
content: "url(./bar.jpg)";
}
以上转化结果不一致。