前端工程化还包括bable
esbuild是新一代的Javascript打包工具。
esbuild以速度快著称,耗时只有webpack的2%到3%。很多工具都内置了esbuild,比如vite和snowpack
esbuild快的原因:
1.使用Go语言编写,可以编译为原生代码
2.解析、打印和源映射完全并行化
3.无须昂贵的数据转换,只需很少的几步完成
目前支持:
commonjs模块、es6模块
使用--bundle与es6模块的静态绑定打包
使用--minify完全压缩(空格、标识符和修饰符)
使用--sourcemap完全支持源映射
支持jsx转向js
使用--define进行编译时标识符替换
使用package.json中的browser字段进行替换
自动监测tsconfig中的baseUrl
安装es-build
npm install esbuild
安装完可以检查一下es build的版本
./node_modules/.bin/esbuild --version
执行打包命令
./node_modules/.bin/esbuild app.jsx --bundle --outfile=out.js
wasm版本
npm install esbuild-wasm
在deno中使用
import * as esbuild from 'https://deno.land/x/esbuild@v0.14.22/mod.js'
const ts = 'let test: boolean = true'
const result = await esbuild.transform(ts, { loader: 'ts' })
console.log('result:', result)
esbuild.stop()
esbuild的插件可以在构建过程中执行,用go或者js编写
let envPlugin = {
name: 'env',
setup(build) {
// Intercept import paths called "env" so esbuild doesn't attempt
// to map them to a file system location. Tag them with the "env-ns"
// namespace to reserve them for this plugin.
build.onResolve({ filter: /^env$/ }, args => ({
path: args.path,
namespace: 'env-ns',
}))
// Load paths tagged with the "env-ns" namespace and behave as if
// they point to a JSON file containing the environment variables.
build.onLoad({ filter: /.*/, namespace: 'env-ns' }, () => ({
contents: JSON.stringify(process.env),
loader: 'json',
}))
let path = require('path')
// Redirect all paths starting with "images/" to "./public/images/"
build.onResolve({ filter: /^images\// }, args => {
return { path: path.join(args.resolveDir, 'public', args.path) }
})
// Mark all paths starting with "http://" or "https://" as external
build.onResolve({ filter: /^https?:\/\// }, args => {
return { path: args.path, external: true }
})
// Load ".txt" files and return an array of words
build.onLoad({ filter: /\.txt$/ }, async (args) => {
let text = await fs.promises.readFile(args.path, 'utf8')
return {
contents: JSON.stringify(text.split(/\s+/)),
loader: 'json',
}
})
build.onStart(() => {
console.log('build started')
})
build.onEnd(result => {
console.log(`build ended with ${result.errors.length} errors`)
})
},
}
require('esbuild').build({
entryPoints: ['app.js'],
bundle: true,
outfile: 'out.js',
plugins: [envPlugin],
}).catch(() => process.exit(1))
snowpack现已停止维护,snowpack的团队主要开发astro
冷启动
在浏览器支持 ES 模块之前,JavaScript 并没有提供的原生机制让开发者以模块化的方式进行开发。这也正是我们对 “打包” 这个概念熟悉的原因:使用工具抓取、处理并将我们的源码模块串联成可以在浏览器中运行的文件。
时过境迁,我们见证了诸如 webpack、Rollup 和 Parcel 等工具的变迁,它们极大地改善了前端开发者的开发体验。
然而,当我们开始构建越来越大型的应用时,需要处理的 JavaScript 代码量也呈指数级增长。包含数千个模块的大型项目相当普遍。我们开始遇到性能瓶颈 —— 使用 JavaScript 开发的工具通常需要很长时间(甚至是几分钟!)才能启动开发服务器,即使使用 HMR,文件修改后的效果也需要几秒钟才能在浏览器中反映出来。如此循环往复,迟钝的反馈会极大地影响开发者的开发效率和幸福感。
当冷启动开发服务器时,基于打包器的方式启动必须优先抓取并构建你的整个应用,然后才能提供服务。
Vite 通过在一开始将应用中的模块区分为 依赖 和 源码 两类,改进了开发服务器启动时间。
依赖 大多为在开发时不会变动的纯 JavaScript。一些较大的依赖(例如有上百个模块的组件库)处理的代价也很高。依赖也通常会存在多种模块化格式(例如 ESM 或者 CommonJS)。
Vite 将会使用 esbuild 预构建依赖。Esbuild 使用 Go 编写,并且比以 JavaScript 编写的打包器预构建依赖快 10-100 倍。
源码 通常包含一些并非直接是 JavaScript 的文件,需要转换(例如 JSX,CSS 或者 Vue/Svelte 组件),时常会被编辑。同时,并不是所有的源码都需要同时被加载(例如基于路由拆分的代码模块)。
更新
基于打包器启动时,重建整个包的效率很低。原因显而易见:因为这样更新速度会随着应用体积增长而直线下降。
一些打包器的开发服务器将构建内容存入内存,这样它们只需要在文件更改时使模块图的一部分失活[1],但它也仍需要整个重新构建并重载页面。这样代价很高,并且重新加载页面会消除应用的当前状态,所以打包器支持了动态模块热重载(HMR):允许一个模块 “热替换” 它自己,而不会影响页面其余部分。这大大改进了开发体验 —— 然而,在实践中我们发现,即使采用了 HMR 模式,其热更新速度也会随着应用规模的增长而显著下降。
在 Vite 中,HMR 是在原生 ESM 上执行的。当编辑一个文件时,Vite 只需要精确地使已编辑的模块与其最近的 HMR 边界之间的链失活[1](大多数时候只是模块本身),使得无论应用大小如何,HMR 始终能保持快速更新。
Vite 同时利用 HTTP 头来加速整个页面的重新加载(再次让浏览器为我们做更多事情):源码模块的请求会根据 304 Not Modified
进行协商缓存,而依赖模块请求则会通过 Cache-Control: max-age=31536000,immutable
进行强缓存,因此一旦被缓存它们将不需要再次请求。
打包
尽管原生 ESM 现在得到了广泛支持,但由于嵌套导入会导致额外的网络往返,在生产环境中发布未打包的 ESM 仍然效率低下(即使使用 HTTP/2)。为了在生产环境中获得最佳的加载性能,最好还是将代码进行 tree-shaking、懒加载和 chunk 分割(以获得更好的缓存)。
虽然 esbuild
快得惊人,并且已经是一个在构建库方面比较出色的工具,但一些针对构建 应用 的重要功能仍然还在持续开发中 —— 特别是代码分割和 CSS 处理方面。就目前来说,Rollup 在应用打包方面更加成熟和灵活。尽管如此,当未来这些功能稳定后,我们也不排除使用 esbuild
作为生产构建器的可能。
vite在构建过程中会有依赖预构建的过程,这个过程有两个目的
1是开发阶段中,Vite 的开发服务器将所有代码视为原生 ES 模块。因此,Vite 必须先将作为 CommonJS 或 UMD 发布的依赖项转换为 ESM。当转换 CommonJS 依赖时,Vite 会执行智能导入分析,这样即使导出是动态分配的(如 React),按名导入也会符合预期效果
2是Vite 将有许多内部模块的 ESM 依赖关系转换为单个模块,以提高后续页面加载性能,这样是出于性能考虑。一些包将它们的 ES 模块构建作为许多单独的文件相互导入。例如,lodash-es
有超过 600 个内置模块!当我们执行 import { debounce } from 'lodash-es'
时,浏览器同时发出 600 多个 HTTP 请求!尽管服务器在处理这些请求时没有问题,但大量的请求会在浏览器端造成网络拥塞,导致页面的加载速度相当慢。
通过预构建 lodash-es
成为一个模块,我们就只需要一个 HTTP 请求了
缓存
预构建文件缓存
Vite 会将预构建的依赖缓存到 node_modules/.vite
。它根据几个源来决定是否需要重新运行预构建步骤:
package-lock.json
,yarn.lock
,pnpm-lock.yaml
,或者 bun.lockb
vite.config.js
相关字段中配置过的NODE_ENV
中的值只有在上述其中一项发生更改时,才需要重新运行预构建。如果出于某些原因,你想要强制 Vite 重新构建依赖,你可以用 --force
命令行选项启动开发服务器,或者手动删除 node_modules/.vite
目录
浏览器缓存
解析后的依赖请求会以 HTTP 头 max-age=31536000,immutable
强缓存,以提高在开发时的页面重载性能。一旦被缓存,这些请求将永远不会再到达开发服务器。如果安装了不同的版本(这反映在包管理器的 lockfile 中),则附加的版本 query 会自动使它们失效。如果你想通过本地编辑来调试依赖项,你可以:
--force
命令以重新构建依赖;本地开发配置
import { defineConfig } from 'vite'
import path from 'path';
export default defineConfig({
resolve: {
alias: [
{ find: "src", replacement: path.resolve(__dirname,'src') },
]
},
server: {
// 端口
port: 8001,
// 是否开启https服务
https: true,
// 代理
proxy: {
'/project/delete': {
target: 'https://www.your-request-url.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/project\/delete/, '')
},
'/project/update': {
target: 'https://www.your-request-url.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/project\/update/, '')
},
}
},
css: {
preprocessorOptions: {
less: {
additionalData: `@import "${path.resolve(__dirname, 'src/theme.module.less')}";`,
javascriptEnabled: true,
}
},
},
})
支持react
@vitejs/plugin-react插件
在vite.config.ts中配置
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
}
按需导入antd样式
vite-plugin-imp
npm i vite-plugin-imp -D
在vite.config.ts中配置
import { defineConfig } from 'vite'
import vitePluginImp from 'vite-plugin-imp'
export default defineConfig({
plugins: [
vitePluginImp({
libList: [
{
libName: 'lodash',
libDirectory: '',
camel2DashComponentName: false
},
{
libName: 'antd',
style(name) {
// use less
return `antd/es/${name}/style/index.js`
}
},
]
})
]
})
react快速刷新
@vitejs/plugin-react-refresh
import { defineConfig } from 'vite'
import reactRefresh from '@vitejs/plugin-react-refresh'
import path from 'path';
export default defineConfig({
plugins: [reactRefresh()],
})
vue3单文件支持
// vite.config.js
import vue from '@vitejs/plugin-vue'
export default {
plugins: [vue()],
}
Vue3支持jsx开发
Vue 用户应使用官方提供的 @vitejs/plugin-vue-jsx 插件,它提供了 Vue 3 特性的支持,包括 HMR,全局组件解析,指令和插槽
// vite.config.js
import vueJsx from '@vitejs/plugin-vue-jsx'
export default {
plugins: [
vueJsx({
// options are passed on to @vue/babel-plugin-jsx
}),
],
}
打包
打包时需要确认自己的项目类型。
如果是一个完成的应用,需要将html打包进项目的,可以使用默认配置。
如果自己的项目是SDK,或者是一个react组件。那么就需要使用另外一种库模式来打包。
如果是node环境的npm包,比如脚手架之类需要执行某些node命令行的配置,都会有所不同
常规配置
import { defineConfig } from 'vite'
import reactRefresh from '@vitejs/plugin-react-refresh'
import path from 'path';
export default defineConfig({
plugins: [reactRefresh()],
build: {
rollupOptions: {
output:{
entryFileNames: `[name].${timestamp}.js`,
chunkFileNames: `[name].${timestamp}.js`,
// css文件名
assetFileNames: `[name].${timestamp}.[ext]`
// 比如你想构建出来的css为dist/index.css,那么你可以这样
// assetFileNames: `index.[ext]`
}
}
},
})
库模式
import { defineConfig } from 'vite'
import path from 'path';
export default defineConfig({
build: {
lib: {
// 入口文件
entry: path.resolve(__dirname, 'src/index.tsx'),
// umd、iife的格式vite要求必须要有name作为导出的全局变量
name: "SpecialEffect",
// 导出格式,默认为["iife","umd"]
formats: ['iife'],
// js打包名称,当然这部分官方文档更加详细
fileName: () => "index.js"
},
// umd格式下,支持将不需要打包的第三方库,排除在外,并指定全部环境
// 提供的全局变量代替,比如以下的例子,不将react打包,由全局React
// 变量提供react库
rollupOptions: {
external: ['react'],
output: {
globals: {
react: 'React'
}
}
}
},
})
安装
npm install -g wp2vite
命令行使用
cd your_workspace/your_project
## 执行wp2vite的命令行
wp2vite
or
wp2vite init
然后启动项目就行
// 安装依赖
npm install
// 启动项目
npm run dev // 如果原先你的项目有dev script,请执行下面的命令
or
npm run vite-start
vite-svg-loader
安装
npm install vite-svg-loader --save-dev
使用
import svgLoader from 'vite-svg-loader'
export default defineConfig({
plugins: [vue(), svgLoader({
svgoConfig: {
multipass: true
}
})
]
})
MF 提供的是一种加载方式,并不是 webpack 独有的,所以社区中已经提供了一个的 Vite 模块联邦方案: vite-plugin-federation,这个方案基于 Vite(Rollup) 也实现了完整的模块联邦能力。
首先需要安装 @originjs/vite-plugin-federation
app1 当前应用: vite.config.js
配置
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import federation from "@originjs/vite-plugin-federation";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
federation({
name: "app1",
remotes: {
app2: "http://localhost:3002/assets/remoteEntry.js",
},
shared: ["react"],
}),
],
});
app2远程应用
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import federation from "@originjs/vite-plugin-federation";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
federation({
name: "app2",
filename: "remoteEntry.js",
library: { type: "module" },
exposes: {
"./App": "./src/App.jsx",
},
shared: ["react"],
}),
],
});
vite-plugin-federation
还可以和 webpack 配合使用
remotes: {
app2: {
external: 'http://localhost:5011/remoteEntry.js',
format: 'var',
from: 'webpack'
}
}
使用时有两点注意:
React 项目中不能使用异构组件(例如 vite 使用 webpack 的组件或者反之),因为现在还无法保证 vite/rollup
和 webpack
在打包 commonjs
框架时转换出 export
一致的 chunk,这是使用 shared
的先决条件
vite
使用 webpack
组件相对容易,但是 webpack
使用 vite
组件时 vite-plugin-federation
组件最好是 esm
格式,因为其他格式暂时缺少测试用例完成测试
vite支持所有的rollup的插件api, rollup的api包括
在启动时调用的钩子
options,buildStart
在每个传入模块请求时被调用的钩子:
resolveId,load,transform
在服务器被关闭时调用
buildEnd、closeBundle
Vite 插件也可以提供钩子来服务于特定的 Vite 目标。这些钩子会被 Rollup 忽略
config
configResolved
在解析 Vite 配置后调用。使用这个钩子读取和存储最终解析的配置。当插件需要根据运行的命令做一些不同的事情时,它也很有用
const examplePlugin = () => {
let config
return {
name: 'read-config',
configResolved(resolvedConfig) {
// 存储最终解析的配置
config = resolvedConfig
},
// 在其他钩子中使用存储的配置
transform(code, id) {
if (config.command === 'serve') {
// dev: 由开发服务器调用的插件
} else {
// build: 由 Rollup 调用的插件
}
},
}
}
configureServer
类型:(server: ViteDevServer) => (() => void) | void | Promise<(() => void) | void>
是用于配置开发服务器的钩子
configureServer
钩子将在内部中间件被安装前调用,所以自定义的中间件将会默认会比内部中间件早运行。如果你想注入一个在内部中间件 之后 运行的中间件,你可以从 configureServer
返回一个函数,将会在内部中间件安装后被调用
const myPlugin = () => ({
name: 'configure-server',
configureServer(server) {
// 返回一个在内部中间件安装后
// 被调用的后置钩子
return () => {
server.middlewares.use((req, res, next) => {
// 自定义请求处理...
})
}
},
})
configurePreviewServer
与 configureServer
相同但是作为预览服务器。它提供了一个 connect 服务器实例及其底层的 http server。与 configureServer
类似,configurePreviewServer
这个钩子也是在其他中间件安装前被调用的。如果你想要在其他中间件 之后 安装一个插件,你可以从 configurePreviewServer
返回一个函数,它将会在内部中间件被安装之后再调用
const myPlugin = () => ({
name: 'configure-preview-server',
configurePreviewServer(server) {
// 返回一个钩子,会在其他中间件安装完成后调用
return () => {
server.middlewares.use((req, res, next) => {
// 自定义处理请求 ...
})
}
},
})
transformIndexHtml
转换 index.html
的专用钩子。钩子接收当前的 HTML 字符串和转换上下文。上下文在开发期间暴露ViteDevServer
实例,在构建期间暴露 Rollup 输出的包
这个钩子可以是异步的,并且可以返回以下其中之一:
{ tag, attrs, children }
)。每个标签也可以指定它应该被注入到哪里(默认是在 <head>
之前){ html, tags }
的对象const htmlPlugin = () => {
return {
name: 'html-transform',
transformIndexHtml(html) {
return html.replace(
/<title>(.*?)<\/title>/,
`<title>Title replaced!</title>`,
)
},
}
}
插件的顺序
一个 Vite 插件可以额外指定一个 enforce
属性(类似于 webpack 加载器)来调整它的应用顺序。enforce
的值可以是pre
或 post
。解析后的插件将按照以下顺序排列:
enforce: 'pre'
的用户插件enforce: 'post'
的用户插件apply
属性指明它们仅在 'build'
或 'serve'
模式时调用
function myPlugin() {
return {
name: 'build-only',
apply: 'build' // 或 'serve'
// apply(config, { command }) {
// 非 SSR 情况下的 build
// return command === 'build' && !config.build.ssr
// }
}
}
安装
npm i magic-string
使用
import MagicString from 'magic-string';
import fs from 'fs'
const s = new MagicString('problems = 99');
s.update(0, 8, 'answer');
s.toString(); // 'answer = 99'
s.update(11, 13, '42'); // character indices always refer to the original string
s.toString(); // 'answer = 42'
s.prepend('var ').append(';'); // most methods are chainable
s.toString(); // 'var answer = 42;'
const map = s.generateMap({
source: 'source.js',
file: 'converted.js.map',
includeContent: true
}); // generates a v3 sourcemap
fs.writeFileSync('converted.js', s.toString());
fs.writeFileSync('converted.js.map', map.toString());
Uncaught Error: Target container is not a DOM element
根元素未找到.原因是: 默认生成的 index.html 中
<div id="root"></div>
https://segmentfault.com/a/1190000039795956
Babel主要做的就是这几点:
Polyfill
实现目标环境中缺少的特性,将环境的特性差异垫平,说白了还是做兼容。比如下面这段代码
// Babel Input: ES2015 arrow function
[1, 2, 3].map((n) => n + 1);
// Babel Output: ES5 equivalent
[1, 2, 3].map(function(n) {
return n + 1;
});
babel的使用场景主要分为两种:命令行和构建工具。
babel为命令行提供了@babel/cli
工具,支持在命令行直接执行babel命令。
npm install --save-dev @babel/core @babel/cli
安装好在项目中执行babel
命令就可以进行转换了。
babel src --out-dir lib # 将src下的文件处理后输出到lib
在构建工具中使用是我们使用babel更常用的方式。
以webpack为例,babel提供了一个loader:babel-loader
,使用这个loader我们就能很轻松实现代码转换。
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
options: {
// 配置项
}
}
]
}
bable的运行方式:
Babel的运行过程分为三个阶段:解析 --- 转换 --- 生成
babel7支持多种方式来进行配置:
babel.config.json
(项目范围).babelrc.json
(相对文件)package.json
中添加babel字段除了json文件之外,babel还支持其他格式的配置文件,比如 .js
,.cjs
(Commonjs)和.mjs
(ESModule),它们相对于json格式更加灵活,可以在配置中进行编程处理,但是会使配置无法静态分析,失去可缓存性。
// babel.config.js
module.exports = {
presets: [],
plugins: []
};
babel虽然有多种方式来写配置文件,但是配置的内容都是类似的,主要包含plugins
和presets
两部分,不过这里需要注意几点:
配置格式:plugins和presets配置项均为数组,如果每项不需要配置的话,直接放入数组(值为项的字符串名称);如果要配置的话,则需要把格式改成数组,该数组的第一个元素是该项的字符串名字,第二个元素是该项的配置对象。这里填写的plugins和presets,babel会自动检查是否已被安装在node_modules下面,当然,也可以使用相对路径来使用本地文件。
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
],
"@babel/plugin-transform-runtime"
]
执行顺序:
plugins
会从前到后执行,presets
会从后向前执行,且plugins
会比presets
先执行
presets
逆序执行的目的是为了保证向后兼容,我们在配置的时候按照规范的时间顺序列出即可,如['es2015', 'stage-1']
,这样就会先使用stage-1
,否则就会出现错误。
短名称:在配置plugin和preset名字的时候,可以省略名字中的babel-plugin-
和preset-
字样,仅使用插件的名字就可以了。
{
"plugins": [
"myPlugin", // 完整名字:"babel-plugin-myPlugin"
"@org/myPlugin" // 完整名字:"@org/babel-plugin-myPlugin"
],
"presets":[
"myPreset", // 完整名字:"babel-preset-myPreset"
"@babel/myPreset" // 完整名字:@babel/preset-myPreset
]
}
babel的转换完全依靠插件,babel拥有的能力完全取决于给它配置了哪些插件。
babel的插件分为两种:
AST
转换阶段。转译插件:转译插件即把特定的语法转换成指定标准的语法。
比如将箭头函数 (x) => x
转换成 function (x) {return x}
,只需要配置箭头函数转译插件即可。
插件的使用大概分为两步:
plugins
属性中,可根据插件文档做相应配置。每个插件的功能是单一的,所以一般转换需求要使用的插件数量比较多,这时候可以考虑presets
@babel/template可以用以字符串形式的代码构建AST树节点,快速优雅开发插件
npm install --save-dev @babel/template
使用
// 引入babel-template
const template = require('babel-template');
// 定义try/catch语句模板
let tryTemplate = `
try {
} catch (e) {
console.log(CatchError:e)
}`;
// 创建模板
const temp = template(tryTemplate);
// 给模版增加key,添加console.log打印信息
let tempArgumentObj = {
// 通过types.stringLiteral创建字符串字面量
CatchError: types.stringLiteral('Error')
};
// 通过temp创建try语句的AST节点
let tryNode = temp(tempArgumentObj);
和webpack一样,bable可以使用自定义插件实现功能
module.exports = function(babel) {
let t = babel.type
return {
visitor: {
Identifier(path, state) {
const name = path.node.name;
// reverse the name: JavaScript -> tpircSavaJ
path.node.name = name
.split("")
.reverse()
.join("");
},
},
};
}
module.exports = function (babel) {
let t = babel.type
return {
visitor: {
AwaitExpression(path) {
// 判断父路径中是否已存在try语句,若存在直接返回
if (path.findParent((p) => p.isTryStatement())) {
return false;
}
let node = path.node;
const asyncPath = path.findParent((p) => p.node.async && (p.isFunctionDeclaration() || p.isArrowFunctionExpression() || p.isFunctionExpression() || p.isObjectMethod()));
let tryNode = temp(tempArgumentObj);
// 获取父节点的函数体body
let info = asyncPath.node.body;
// 将函数体放到try语句的body中
tryNode.block.body.push(...info.body);
// 将父节点的body替换成新创建的try语句
info.body = [tryNode];
}
}
}
}
https://juejin.cn/post/7155434131831128094#heading-17
在实际的开发中,我们如果用ES6来进行开发的话,那需要转换的语法是非常多的,难道要一个一个去配置吗?还要不要配陪妹子了?
这个时候,就该presets登场了。presets可以理解为一组插件的集合,就像你去德克士直接点一个全家桶,而不必一个一个给服务员说要鸡腿、鸡翅、薯条...(如果想和服务员小姐姐多待一会,也不是不可以)
配置文件:通过配置文件的targets属性指定目标环境
{
"presets": [
["env", {
"targets": {
"browsers": ["last 2 versions", "safari >= 7"],
"node": "12.10"
}
}]
]
}
browserslistrc:如果是浏览器或者Electron
,官方推荐使用.browserslistrc
文件来指定目标环境(可以和autoprefixer、stylelint等其他工具共享配置)。
不过如果在配置文件中设置了 targets
或 ignoreBrowserslistConfig
,.browserslistrc
中配置的内容将不会生效。
presets中的其他配置:
"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false
,默认为"auto"
。useBuiltIns
:此选项配置如何@babel/preset-env
处理polyfill,在后面介绍polyfill我们会详细介绍。corejs
:用于指定corejs的版本,仅当使用了useBuiltIns: usage
或者
useBuiltIns: entry
此配置才有效,使用时一定要配置正确的版本,否则会报错。
当前默认的版本是2.0
,不过建议使用3.0
版本,因为2.0已经不会再添加新的特性了,如果使用了一些较新的语法,将会无法转换。
@babel/preset-env
这个是我们最常用的,也是官方现在推荐的preset,它是一个智能预设,我们无需再关心特定环境下所需的转换插件(只需要指定目标环境),就可以使用最新的语法。除了能够减少使用难度,还能够使转换出来的代码体积更小。
preset-env会根据配置的目标环境中缺少的功能,选择对应的插件进行必要的转换和polyfill
,而目标环境支持的特性就不会转换,而不是无脑的一把梭哈。
前面我们的配置其实是不完美的,转换出来的代码直接在浏览器运行是可能会出问题的。这是因为babel默认只转换了语法,而对于一些新的API是并没有处理的,如Generator、Set、Maps、Proxy、Promise 等全局对象及其方法都不会转码,如果我们在代码中使用到了就会报错。
此时,我们就需要做Polyfill,也就是所谓的'垫片',作用就是把浏览器的差异垫平,人话就是通过模拟为这些浏览器补全缺失的全局对象及API。
@babel/plugin-transform-runtime
是一个可重复使用Babel注入的帮助程序的插件,避免重复注入,以节省代码大小。通常还要搭配@babel/runtime
一起使用。
安装
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime # 提供缺失的特性,须要在生产环境中安装
配置
// babel.config.json
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
]
],
"plugins": [["@babel/plugin-transform-runtime"]]
}
Babel-plugin-import
使用 antd
时, 需要加载组件的样式 antd/dist/antd.css
, 但是我们大部分时候不需要使用到antd
所有的组件, 更不需要载入所有组件的样式。
所以antd
提供了按需引入
import Button from 'antd/lib/button';
import 'antd/lib/button/style';
而babel-plugin-import
能够帮助我们在引入组件的时候自动加载相关样式。,从而不必全局引入antd、lodash等
安装
npm install babel-plugin-import --save-dev
在babelrc中配置
'plugins': [
['import',{'libraryName': 'antd', 'style': true,'libraryDirectory': 'lib'},'antd']
['import',{'libraryName': 'antd-mobile', 'libraryDirectory': 'lib'},'antd-mobile']
]
sucrase支持的类型
jsx => 转换jsx/tsx
typescript => 将 ts -> js
Flow => check types
imports => cjs
react-hot-loader => 执行react-hot-loader项目中的等效转换
jest => 提升期望的jest方法调用上面的导入方式与babel-plugin-jest-hoist相同
安装
yarn add --dev sucrase # Or npm install --save-dev sucrase
node -r sucrase/register main.ts
使用
yarn add --dev sucrase ts-node typescript
./node_modules/.bin/ts-node --transpiler sucrase/ts-node-plugin main.ts
在ts-node中使用
ts-node --transpiler sucrase/ts-node-plugin
使用
import {transform} from "sucrase";
const compiledCode = transform(code, {transforms: ["typescript", "imports"]}).code;
检查引入的node包打包之后所占的体积
https://github.com/pastelsky/bundlephobia
swc是一个类似babel的JavaScript/TypeScript编译器,但是由于swc使用Rust实现,所以具备比babel出色的性能。
webpack与bable的性能瓶颈都在于JS语言,出现了go实现的esbuild与Rus实现的swc等工具,esbuild想要开辟一个构建工具性能的新时代,创建一个易用的现代打包器,swc的目标则是是替代babel,在官方文档的Comparison with babel这个单元,我们可以看到swc团队将相当一部分bable的插件重写了以增强swc的能力。
对比 babel,swc 有至少 10 倍以上的性能优势
同时swc也提供了一个构建打包工具spack,这个工具并不是一个全面的构建工具,但是我们可以通过这个工具简单的体验一下swc的能力
我们在简单的工具库编写时,可以使用swc提供的spack命令来快速打包出commonjs, es6, amd, umd
安装
npm i -D @swc/core @swc/cli
## yarn add --dev @swc/core @swc/cli
在项目的根目录创建一个 .swcrc
文件,配置信息如下
[
{
"jsc": { // 编译规则
"target": "es5", // 输出js的规范
"parser": {
// 除了 ecmascript,还支持 typescript
"syntax": "ecmascript",
// 是否解析jsx,对应插件 @babel/plugin-transform-react-jsx
"jsx": false,
// 是否支持装饰器,对应插件 @babel/plugin-syntax-decorators
"decorators": false,
// 是否支持动态导入,对应插件 @babel/plugin-syntax-dynamic-import
"dynamicImport": false,
// ……
// babel 的大部分插件都能在这里找到对应配置
},
"minify": {}, // 压缩相关配置,需要先开启压缩
},
"env": {
"targets": {
"chrome": "58",
"ie": "11"
}
}
"minify": true // 是否开启压缩
}
]
配置spack
const { config } = require("@swc/core/spack");
module.exports = config({
//入口文件
entry: __dirname + "/src/index.ts",
//输出
output: {
path: __dirname + "/dist",
},
// swc编译配置
options:{
jsc: {
//解析配置
parser: {
syntax: "typescript", //输入文件格式
tsx: false, // 是否支持tsx
dynamicImport: false, //是否支持动态导入
decorators: false, //是否支持装饰器
},
transform: null,
target: "es5", //转译目标
loose: false,
externalHelpers: false,
keepClassNames: false
},
// 输出文件配置
module: {
type: "commonjs",
strict: false,
strictMode: true,
lazy: false,
noInterop: false,
ignoreDynamic: false
}
}
});
在package.json中增加
"scripts": {
"build": "spack"
}
SWC 也提供了 Webpack Loder,可以和 Webpack 一起使用
安装 @swc/core
和 swc-loader
cnpm install --save-dev @swc/core swc-loader
安装完成后在 Webpack 的配置文件中添加 SWC 的 Loder 配置
const path = require('path');
module.exports = {
// 入口文件
entry: './src/main.js',
output: {
// 打包完成后输出的目录
path: path.resolve(__dirname, 'dist'),
// 打包完成后的 JS 文件名
filename: 'index.js',
},
module: {
rules: [
// SWC 的 Loder 配置
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'swc-loader',
},
},
],
},
};
SWC 的配置还是和上面一样的使用 .swcrc
的配置
运行打包后 swc-loder 就会调用 swc-core 转码
SWC 和 Webpack 一起使用也还是不能转换 ES6 中新增的 Promise
之类的功能,所以还需要使用 polyfill 或 core-js
因为 core-js 可以按需引入,而 polyfill 需要配合 Babel 才能实现按需引入,所以这里就使用 core-js
cnpm install --save-dev core-js
安装完成后在 main.js
中引入需要的库,比如我用到了 Promise
,我就需要引入 core-js 的 Promise
转换库
import 'core-js/features/promise';
core-js 的转换模块就在 core-js/features
目录中
打包完成后就可以在 IE 浏览器中使用 Promise
之类的功能了
Rome是 Babel 作者做的基于 Nodejs 的前端基建全家桶,包含但不限于 Babel, ESLint, webpack, Prettier, Jest。
rome
是个全家桶 API,所以你只需要 yarn add rome
就完成了所有环境准备工作。
rome bundle
打包项目。rome compile
编译单个文件。rome develop
调试项目。rome parse
解析文件抽象语法树。rome analyzeDependencies
分析依赖。Rome 还将文件格式化与 Lint 合并为了 rome check
命令,并提供了友好 UI 终端提示。