Farm:Rust构建的下一代前端构建器 Nov 16 2024 笔记 0 comment ### 一、Farm及其优势 #### 1. 什么是Farm Farm 是一个非常快的基于 Rust 的 Web 构建工具,用于构建web和nodejs应用。Fram提供快速的编译和hmr,为大型前端项目提供更加极致的编译效率。 #### 2. Farm与传统构建器的区别 ##### 2.1 vite/snowpack 此类工具主要有下面三个特性: - 使用原生 ESM,在 dev server 启动时不对源文件进行编译和打包,源文件在入口模块执行时才会通过浏览器请求 dev server 编译,编译后的产物返回给浏览器 - HMR 时,只重新编译一个模块,这样 HMR 的时间约等于一个模块的编译时间 - 对外部依赖(如 node_modules 下的依赖)进行预打包 依然存在的弊端: - 大量的模块请求:对于一个大型项目,使用上述策略 1、2,首次启动可能需要加载数千个模块,使用原生模块系统加载数千个模块会导致浏览器卡住甚至崩溃。虽然 Dev Server 的启动快了,但是请求时的模块的首次编译依然会耗费大量时间,会有数十秒起步的白屏,编译时间只是转移了,并没有消失。 - 开发和生产之间的不一致:出于兼容性和请求数量、请求层级的考虑,原生 ES 模块在大多数情况下无法在生产中使用。 因此 vite 选择在生产环境选择用 rollup 打包。 这就带来了不一致,当这种不一致导致生产错误时,调试起来非常困难且非常痛苦。 而 vite 在 dev 中使用 esbuild,在生产中使用 rollup,这更加就放大了不一致性。 - 拆包优化困难: 受限于 Rollup,拆包配置不够灵活、易用。 ##### 2.2 Farm Farm的设计理念: - 性能优先:一切都会用 Rust 编写,只有少数不是性能瓶颈的部分会用 JS 编写 - 一致性:默认情况下确保开发和生产完全相同,您在开发中看到的将与您在生产中得到的相同。 - 局部打包:Farm 的打包目标不是将所有东西打包在一起,而是限制资源的请求数量。 Farm 会根据依赖关系和资源大小将项目捆绑成 20-30 个小资源,在不损失缓存粒度的情况下获得最佳的资源加载性能。 - 所有资源视为一等公民:Farm 不再需要将所有内容转换为 Javascript,它将任何内容视为一等公民,如 html、js/jsx/ts/tsx、css/scss、png/svg/...都是 Farm 支持的基础模块,更多类型的模块可以通过插件支持。 - 兼容性:Farm 将适用于旧版 (ES5) 和现代浏览器。 - Rollup 风格的插件系统:轻松创建自己的插件,并轻松从 rollup/vite/webpack 迁移您的插件/项目。兼容 Vite/Rollup/Unplugin 插件 ### 二、Farm的基础配置 #### 1. 配置文件规范 默认情况下,Farm 从项目根目录下的“farm.config.ts|js|mjs”文件中读取配置。 import { defineConfig } from "@farmfe/core"; export default defineConfig({ root: process.cwd(), // // 编译的根目录 // 编译选项 compilation: { //... }, // 开发服务器选项 server: { hmr: true, //... }, // 插件配置 plugins: [], }); #### 2. 编译(complilation)配置 所有与编译相关的配置都在 compilation 配置对象中,这里介绍一些常用的配置,弯针配置参考官方文档 [`Farm | 编译配置`](https://farm-fe.github.io/zh/docs/config/compilation-options#resolve) 配置文件示例 // ... export default defineConfig({ compilation: { //... } // ... }); ##### 2. 1 Input、output Input项目的入口点。 Input 的文件可以是html、ts/js/tsx/jsx、css 或通过插件支持的其他文件。 import { defineConfig } from "@farmfe/core"; export default defineConfig({ compilation: { input: { index: "./index.html", //对于多页面程序,可配置多个页面的入口文件 //about: "./about.html", }, }, // .. }) Ouput项目的输出目录。ouput的interface 定义如下: interface OutputOptions { // 局部打包后,入口文件所在资源的文件名配置 entryFilename?: string; // 局部打包后,除入口资源外的其他资源输入文件名配置 filename?: string; // 输入目录,默认'dist' path?: string; // public path:资源加载前缀 publicPath?: string;//publicPath: process.env.NODE_ENV === 'production' ? 'https://cdn.com' : '/' // 静态资源文件名配置 assetsFilename?: string; // 目标执行环境,浏览器或者 Node targetEnv?: "browser" | "node"; // 输出模块格式 format?: "cjs" | "esm"; } > ##### ouput.targetEnv > > **默认值:** `"browser-es2017"` > > > > Farm 会自动为您指定的 `targetEnv` 注入 `polyfill` 和降级语法(对于脚本和 css),支持的 `targetEnv` 如下: > > > > `browser:` > > - **`browser-es2017`**:将项目编译到原生支持 `async wait` 的浏览器。 > - **`browser-es2015`**:将项目编译到原生支持 `es6 features` 的浏览器。 > - **`browser-legacy`**:将项目编译为`ES5`,例如`IE9`。 请注意,这可能会引入大量的填充,从而使生产规模更大。 确保您确实需要支持 `IE9` 等旧版浏览器。 > - **`browser-esnext`**:将项目编译到最新的现代浏览器,不会注入任何polyfill。 > - **`browser`**:`browser-es2017`的别名 > > > > `nodejs:` > > - **`node16`**:将项目编译到`Node 16`。 > - **`node-legacy`**:将项目编译到 `Node 10` 。 > - **`node-next`**:将项目编译到最新的 Node 版本,不会注入任何 polyfill。 > - **`node`**:`node16`的别名 ##### 2.2 resolve 配置解析依赖相关的选项,interface定义如下: interface ResolveOptions { extensions?: string[]; alias?: Record<string, string>; mainFields?: string[]; conditions?: string[]; symlinks?: boolean; strictExports?: boolean; } > ##### resolve.extensions > > 配置解析依赖时的后缀,默认值:`["tsx", "ts", "jsx", "js", "mjs", "json", "html", "css"]`,当解析一个文件时,如./index,未找到时,会按照配置顺序依次匹配,直到找到。 > > ##### resolve.alias > > > export default defineConfig({ > compilation: { > resolve: { > alias: { > "/@": path.join(process.cwd(), "src"), > stream$: "readable-stream", > "$__farm_regex:^/(utils)$": path.join(process.cwd(), "src/$1"), > }, > }, > }, > }); > > > 配置解析路径别名。默认值:`{}`。如`/@/pages` 将会被替换为,`/root/src/pages`。 ##### 2.3 define 全局变量注入,配置的变量名和值将会在编译时注入到产物中。Farm 默认注入 `process.env.NODE_ENV` 以及部分 Farm 自身使用的变量比如 `FARM_HMR_PORT` export default defineConfig({ compilation: { define: { MY_VAR: 123, }, }, }); ##### 2.4 mode 默认值: 对于 `start、watch` 命令是 `development`,对于 `build` 命令是 `production` 配置编译模式,为了优化开发时性能,在没有手动配置生产优化相关选项(minify,tree shake 等)时,默认在 development 下会禁用生产环境优化比如压缩和 tree shake,在 production 模式下启用。 ##### 2.6 assets 配置额外视为静态资源的文件后缀,通过`assets.include`配置。如下面把markdown文件视为静态文件。 export default defineConfig({ compilation: { assets: { include: ["md"], }, }, }); ##### 2.7 css、script > ##### css > > `module.path:` > > 默认值:`["\\.module\\.(css|scss|sass|less)"]` > > 配置哪些路径对应的模块会被视为 CSS 模块。配置使用正则字符串。默认是以 `.module.(css|scss|sass|less) `结尾的文件。 > > `module.indentName:` > > 配置生成的 CSS Modules 类名,默认是 `[name]-[hash]`,`[name]`, `[hash]` 为占位符(也是目前支持的所有占位符)。`[name]` 表示原始类名,`[hash]` 表示该css 文件 id 的 hash。 > > ##### script > > `target:`配置 Farm 解析 `js/jsx/ts/tsx` 的 AST 以及生成代码时支持的 ES 语法版本。 可选值:`es5`, `es6`, `es2015` - `es2023`, `esnext`。 > > ##### 2.8 partialBundling 配置 Farm 局部打包的行为。 export interface FarmPartialBundlingConfig { targetConcurrentRequests?: number; targetMinSize?: number; targetMaxSize?: number; groups?: { name: string; test: string[]; groupType?: "mutable" | "immutable"; resourceType?: "all" | "initial" | "async"; }[]; enforceResources?: { name: string; test: string[]; }[]; enforceTargetConcurrentRequests?: boolean; enforceTargetMinSize?: boolean; immutableModules?: string[]; } > ##### targetConcurrentRequests > > `默认值:25`。Farm 尝试生成尽可能接近此配置值的资源数量,控制初始资源加载或动态资源加载的并发请求数量。 > > ##### targetMaxSize、targetMinSize > > `默认值:targetMinSize:20 * 1024 bytes, 20 KB、targetMaxSize:1500 * 1024 bytes, 1500 KB`分别代表minify 和 gzip 之前生成的资源的最大和最小体积。`targetMinSize` 并不一定保证满足,可以配置`enforceTargetMinSize`可用于强制限制最小的大小。 ##### 2.9 lazyCompilation、treeShaking、minify > ##### lazyCompilation > > 默认值: 在开发模式是 `true`,构建模式是 `false`是否启用懒编译,配置为 false 关闭。参考 [懒编译](https://farm-fe.github.io/zh/docs/features/lazy-compilation)。 > > ##### treeShaking > > 默认值: 在开发模式是 `false`,构建模式是 `true`是否启用 tree shake,配置为 false 关闭。参考 [Tree Shake](https://farm-fe.github.io/zh/docs/advanced/tree-shake)。 > > ##### minify > > 默认值: 在开发模式是 `false`,构建模式是 `true` > > 类型: `bool | JsMinifyOptions`是否启用压缩,开启后将会对产物进行压缩和混淆。参考 [压缩](https://farm-fe.github.io/zh/docs/advanced/tree-shake)。 > > ###### minify.include > > 默认:`[],类型string[]`包含需要压缩的模块,默认全部,仅在 `minify.mode` 为 `minify-module` 生效 > > ###### minify.exclude > > 默认:`["*.min.(js|css|html)"],类型string[]`排除不需要压缩模块,仅在 `minify.mode` 为 `minify-module` 生效 > > ###### minify.mode > > 默认值: `'minify-module',类型'minify-module' | 'minify-resource-pot'`。`minify-module` 模块级别 `minify`,可以通过参数控制需要 minify 哪些模块,压缩的更为精细,效率更好。`minify-resource-pot·` `ResourcePot` 级别 `minify`,无法通过参数控制具体的模块 ##### 2.10 presetEnv 配置对哪些模块注入语法降级。默认在开发环境为false,构建模式为true。presetEnv的接口定义如下: type FarmPresetEnvConfig = | boolean | { include?: string[]; exclude?: string[]; // TODO using swc's config options?: any; assumptions?: any; }; > ##### include > > 默认值:`[]` 配置额外包含哪些模块进行语法降级。 > > ##### exclude > > 默认值:`['node_modules/']`。默认不对node_modules目录进行polyfill,如果需要请使用include添加。 > > ##### option > > 默认值:降级到es5,[详细参考](https://swc.rs/docs/configuration/compilation#env%E3%80%82) ##### 2.11 comments 配置如何处理注释,默认值`license`: - true: 保留所有注释 - false: 删除所有注释 - license: 保留所有 LICENSE 注释, 移除所有非 LICENSE 注释 #### 3. 开发服务器(devServer)配置 配置Farm 的运行相关配置项,所有的配置都在`server`中进行配置。如下是一个配置示例: import type { UserConfig } from "@farmfe/core"; function defineConfig(config: UserConfig) { return config; } export default defineConfig({ server: { port: 9000, // ... }, }); ##### 3.1 port dev server的运行监听端口,默认值:`9000` ##### 3.2 hmr 默认对于 `farm start`命令开启,其他命令关闭。 启用 HMR,将会监听编译过程中涉及到的模块的变动,当模块变化时,自动触发重编译并将结果推送给 Farm Runtime 进行更新。端口和主机等配置可在hmr中进行配置,比如: import type { UserConfig } from "@farmfe/core"; function defineConfig(config: UserConfig) { return config; } export default defineConfig({ server: { hmr:{ port:9801,//默认值 host:"localhost"//默认值 } // ... }, }); ##### 3.3 proxy 配置服务器代理。基于 [http-proxy](https://github.com/http-party/node-http-proxy?tab=readme-ov-file#options) 实现,具体选项参考其文档,示例: import type { UserConfig } from "@farmfe/core"; function defineConfig(config: UserConfig) { return config; } export default defineConfig({ server: { proxy: { '/api':{ target:"https://xxzxka.eu.org/app", changeOrigin:true, pathRewrite: (path: any) => path.replace(/^\/api/, ""), } }, }, }); ##### 3.4 open 配置完成编译运行后是否自动打开浏览器到对应的页面。默认值:`false` ##### 3.5 plugins 配置 Farm 的 Dev Server 插件,通过 Dev Server 插件可以扩展 DevServer 的上下文,添加 middleware 等。 export function hmrPlugin(devServer: DevServer) { const { config, logger } = devServer; if (config.hmr) { devServer.ws = new WebSocketServer({ port: config.hmr.port, host: config.hmr.host, }); devServer.app().use(hmr(devServer)); devServer.hmrEngine = new HmrEngine( devServer.getCompiler(), devServer, logger ); } } #### 4. 通用配置 ##### 4.1 目录(dir)配置 ##### envdir、envPrefix 配置目录以加载 `.env`、`.env.development`、`.env.Production` 文件。 默认情况下它与 root 相同。 import { defineConfig } from '@farmfe/core'; import { resolve } from 'path'; export default defineConfig({ envPrefix: ['FARM_', 'CUSTOM_PREFIX_', 'NEW_'], envDir: resolve(process.cwd(), './env'), }); ##### publicDir 默认:`public` `publicDir` 下的文件将始终被视为静态资源。 在 dev 时可以通过 dev server 直接访问,在构建时会将其复制到 [`output.path`](https://farm-fe.github.io/zh/docs/config/compilation-options#outputpath)。 比如将字体等静态资源添加到 `public` 目录,并将它们用作 `/xxx.ttf` 。 ##### 4.2 插件(Plugins)配置 ##### Rust插件 Rust插件使用package name配置使用,默认值`[]`,在字段`plugins`中声明配置要使用的插件。当需要配置插件项时,使用[packageName,optionsObject]格式。具体如下: export default defineConfig({ // ... plugins: [ //基本语法配置 "@farmfe/plugin-sass" // 使用数组语法来配置 Rust 插件 [ // Rust 插件的名称 "@farmfe/plugin-sass", // Rust 插件的选项 { additionalData: '@use "@/global-variables.scss";' } ], ], }); ##### Js插件 Js插件本质就是一个函数对象。默认值`{}`,在字段`plugins`中声明配置要使用的插件。 import farmPostcssPlugin from "@farmfe/js-plugin-postcss"; export default defineConfig({ plugins: [ farmPostcssPlugin({ // ... 配置 postcss 选项 }) ], }); 可以通过`priority`配置插件的优先级,插件本身返回一个配置对象,可以结合对象的展开和`priority`组合,如farmPostcssPlugin: import farmPostcssPlugin from "@farmfe/js-plugin-postcss"; export default defineConfig({ plugins: [ { ...farmPostcssPlugin( { //插件的配置项目 } ), //内部插件的优先级都是100,如果想让插件先执行,就设置大于100,否则设置小于100。 priority:100 } ], }); > **提示** > > Js插件必须要实现filter,因为Js的速度相较于Rust很慢,通过filter过滤掉不需要处理的类型,可以提高运行速度。 ##### Vite/Rollup/Unplugin插件 (1) 使用vite插件 Farm 兼容 Vite 插件,默认值`[]`,在字段`vitePlugins`中声明配置要使用的插件。首先安装vite插件: npm add @vitejs/plugin-vue @vitejs/plugin-vue-jsx vite -D 然后在`farm.config.ts`中的`vitePlugins`直接配置: import vue from '@vitejs/plugin-vue', import vueJsx from '@vitejs/plugin-vue-jsx'; export default defineConfig({ // 配置vite插件 vitePlugins: [ vue(), vueJsx() ] }); 为了提高 vite 插件的性能,您可以使用返回`过滤器`的`函数语法`: import vue from '@vitejs/plugin-vue', // // 使用Farm 中 Vite 插件的函数语法 function configureVitePluginVue() { // 返回插件及其过滤器 return { // 使用 vue 插件 vitePlugin: vue(), // 为其配置过滤器。 不匹配的模块路径将被跳过。 filters: ['\\.vue$', '\\\\0.+'] }; } export default defineConfig({ vitePlugins: [ configureVitePluginVue ] }); (2)使用unplugin插件 在Farm中使用unplugin插件,在字段`vitePlugins`中声明配置要使用的插件。,可以实现引入第三方组件库以及AutoImport等功能。 安装: npm add unplugin-auto-import unplugin-vue-components -D 使用: import vue from '@vitejs/plugin-vue', import AutoImport from 'unplugin-auto-import/vite' import Components from 'unplugin-vue-components/vite' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' export default defineConfig({ vitePlugins: [ vue(), // ... AutoImport({ resolvers: [ElementPlusResolver({ importStyle: 'sass' })], }), Components({ resolvers: [ElementPlusResolver({ importStyle: 'sass' })], }), ] }); 本文由 yuin 创作,本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。