前端打包工具的介绍

Browserify, Grunt, Gulp, Webpack, rollUp, vitejs

Browserify

browserify 是早期的模块打包工具,是先驱者,踏实的浏览器端使用 CommonJS 规范的格式组织代码成为可能。在这之前,因为 CommonJS 与浏览器特性的不兼容问题,更多使用的是 AMD 规范,当然后来又发展了ES6模块规范

假设有如下模块 add.js 和文件 test.js,test.js 使用 CommonJS 规范导入了模块 add.js

1
2
3
4
5
6
7
8
// add.js
module.exports = function(a, b) {
return a + b
};

// test.js
var add = require('./add.js');
console.log(add(1, 2)); // 3

我们知道,如果直接执行是执行不成功的,因为浏览器无法识别 CommonJS 语法,而 browserify就是用来处理这个问题的,他将 CommonJS 语法进行装换,在命令行执行如下

1
browserify test.js > bundle.js

生成的 bundle.js 就是已经处理完毕,可供浏览器执行使用的文件,只需要将它插入到<script>即可。

Grunt

Grunt 的出现早于 Gulp,Gulp 是后起之秀。他们本质都是通过 JavaScript 语法实现了 shell script 命令的一些功能。比如利用 jshint 插件实现 JavaScript 代码格式检查这一功能。早期需要手动在命令行中输入 jshint test.js,而 Grunt 则通过 Gruntfile.js 进行配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Gruntfile.js
module.exports = function(grunt) {
grunt.initConfig({
// js格式检查任务
jshint: {
src: 'src/test.js'
}
// 代码压缩打包任务
uglify: {}
});
// 导入任务插件
grunt.loadnpmTasks('grunt-contrib-uglify');
// 注册自定义任务, 如果有多个任务可以添加到数组中
grunt.regusterTask('default', ['jshint'])
}

Gulp

Gulp 吸取了 Grunt 的优点,拥有更简便的写法,通过流(stream)的概念来简化多任务之间的配置和输出,让任务更加简洁和容易上手。Gulp 还是工具链、构建工具,可以配合各种插件做js压缩,css压缩,less编译,替代手工实现自动化工作。通过配置 gulpfile.js 文件来实现,一个简单的 gulpfile.js 配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// gulpfile.js
var gulp = require('gulp');
var jshint = require('gulp-jshint');
var uglify = require('gulp-uglify');

// 代码检查任务 gulp 采取了pipe 方法,用流的方法直接往下传递
gulp.task('lint', function() {
return gulp.src('src/test.js')
.pipe(jshint())
.pipe(jshint.reporter('default'));
});

// 压缩代码任务
gulp.task('compress'function() {
return gulp.src('src/test.js')
.pipe(uglify())
.pipe(gulp.dest('build'));
});

// 将代码检查和压缩组合,新建一个任务
gulp.task('default', ['lint', 'compress']);

WebPack

Webpack 可以设置入口文件,然后自动找寻依赖进行编译。webpack插件较多,可以完成各种编译,文件优化等功能

Webpack的配置

  • 入口(entry)
  • 输出(output)
  • loader
  • 插件(plugin)
  • 模式(mode)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
{
entry: {
driedFishLottery: '/Users/maomao/code/activities/src/views/driedFishLottery/index.js',
impactChallenge: '/Users/maomao/code/activities/src/views/impactChallenge/index.js',
luckyKick: '/Users/maomao/code/activities/src/views/luckyKick/index.js',
qingmingact: '/Users/maomao/code/activities/src/views/qingmingact/index.js',
'react-funcA': '/Users/maomao/code/activities/src/views/react-funcA/index.js',
summerCelebration: '/Users/maomao/code/activities/src/views/summerCelebration/index.js',
touchFish: '/Users/maomao/code/activities/src/views/touchFish/index.js',
wordsOfLove: '/Users/maomao/code/activities/src/views/wordsOfLove/index.js'
},
mode: 'production',
devServer: {
host: '0.0.0.0',
port: 3000,
historyApiFallback: true,
open: true,
useLocalIp: true,
openPage: 'driedFishLottery.html'
},
resolve: { alias: { '@': '/Users/maomao/code/activities/src' } },
devtool: 'inline-source-map',
output: {
filename: 'js/[name]-bundle.[chunkhash:8].js',
path: '/Users/maomao/code/activities/dist',
publicPath: '/gameActivity/'
},
module: {
rules: [
...
]
},
plugins: [
...
],
optimization: {
splitChunks: { ... },
minimize: true,
minimizer: [ [CssMinimizerPlugin], [UglifyJsPlugin] ]
},
performance: { maxEntrypointSize: 500000, maxAssetSize: 400000 }
}

ps: webpack5 后输出的文件默认会有箭头函数,要么回退到webpack4,或者在output下面加上设置

1
2
3
4
5
6
7
output: {
filename: 'js/bundle.js',
path: path.resolve(__dirname, 'dist'),
environment: {
arrowFunction: false
}
}

相关配置: https://webpack.docschina.org/configuration/output/#outputenvironment

webpack plugin的开发

开发了个编译后自动上传ftp的插件

https://www.npmjs.com/package/uploadplugin

https://gitee.com/delicious28/upload-plugin

plugins complier的一些钩子
https://webpack.docschina.org/api/compiler-hooks/

rollUp

rollup 所有资源放同一个地方,一次性加载,利用 tree-shake特性来 剔除未使用的代码,减少冗余

1
2
3
4
5
6
7
8
9
10
11
12
13
export default {
// 核心选项
input, // 必须
plugins,

output: { // 必须 (如果要输出多个,可以是一个数组)
// 核心选项
file, // 必须
format, // 必须
name,
globals,
}
};

优点

  • Tree Shaking: 自动移除未使用的代码, 输出更小的文件
  • Scope Hoisting: 所有模块构建在一个函数内, 执行效率更高
  • 可以一次输出多种格式:IIFE, AMD, CJS, UMD, ESM

缺点

  • 插件比较少(相对于webpack)

vite

对于Vite,在开发过程中没有捆绑。源代码中的ES Import语法直接提供给浏览器,浏览器通过原生的<script module>支持解析这些语法,并为每次导入发起HTTP请求。dev服务器拦截请求,并在必要时执行代码转换。

优点

  • 超快速的服务启动(因为启动时啥也不编译)
  • 开箱即用 (基于rollUp 开发了less,jsx, ts 的 plugin)

缺点

  • 通过服务器拦截请求根据后缀来进行编译,所以在引用时一定要写清后缀(比如.vue .jsx)
  • 因为编译是基于rollUp的,所以插件也还是少
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
export default defineConfig({
build: {
rollupOptions: {
input: {
main: 'main/index.html',
main2: 'main2/index.html',
luckyKick: 'luckyKick/index.html'
},
},
sourcemap:true
},
resolve:{
alias:{
"@": '/src'
}
},
clearScreen:false,
plugins: [
plugin(),
reactRefresh()
]
})

对应生成的rollUp配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
{
build: {
target: [ 'es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1' ],
polyfillDynamicImport: false,
outDir: 'dist',
assetsDir: 'assets',
assetsInlineLimit: 4096,
cssCodeSplit: true,
sourcemap: true,
rollupOptions: { input: [Object] },
commonjsOptions: { include: [Array], extensions: [Array] },
dynamicImportVarsOptions: { warnOnError: true, exclude: [Array] },
minify: 'terser',
terserOptions: {},
cleanCssOptions: {},
write: true,
emptyOutDir: null,
manifest: false,
lib: false,
ssr: false,
ssrManifest: false,
brotliSize: true,
chunkSizeWarningLimit: 500,
watch: null
},
resolve: { dedupe: undefined, alias: [ [Object], [Object] ] },
clearScreen: false,
plugins: [
{
name: 'alias',
buildStart: [Function: buildStart],
resolveId: [Function: resolveId]
},
{
name: 'react-refresh',
enforce: 'pre',
configResolved: [Function: configResolved],
resolveId: [Function: resolveId],
load: [Function: load],
transform: [Function: transform],
transformIndexHtml: [Function: transformIndexHtml]
},
{
name: 'vite:dynamic-import-polyfill',
resolveId: [Function: resolveId],
load: [Function: load],
renderDynamicImport: [Function: renderDynamicImport]
},
{
name: 'vite:resolve',
configureServer: [Function: configureServer],
resolveId: [Function: resolveId],
load: [Function: load]
},
{
name: 'vite:html',
resolveId: [Function: resolveId],
load: [Function: load]
},
{
name: 'vite:css',
configureServer: [Function: configureServer],
buildStart: [Function: buildStart],
transform: [AsyncFunction: transform]
},
{ name: 'vite:esbuild', transform: [AsyncFunction: transform] },
{ name: 'vite:json', transform: [Function: transform] },
{
name: 'vite:wasm',
resolveId: [Function: resolveId],
load: [AsyncFunction: load]
},
{
name: 'vite:worker',
load: [Function: load],
transform: [AsyncFunction: transform]
},
{
name: 'vite:asset',
buildStart: [Function: buildStart],
resolveId: [Function: resolveId],
load: [AsyncFunction: load],
renderChunk: [Function: renderChunk],
generateBundle: [Function: generateBundle]
},
{ name: 'my-example', configResolved: [Function: configResolved] },
{ name: 'vite:define', transform: [Function: transform] },
{
name: 'vite:css-post',
buildStart: [Function: buildStart],
transform: [Function: transform],
renderChunk: [AsyncFunction: renderChunk],
generateBundle: [AsyncFunction: generateBundle]
},
{
name: 'vite:build-html',
transform: [AsyncFunction: transform],
generateBundle: [AsyncFunction: generateBundle]
},
{
name: 'commonjs',
buildStart: [Function: buildStart],
resolveId: [Function: resolveId],
load: [Function: load],
transform: [Function: transform],
moduleParsed: [Function: moduleParsed]
},
{
name: 'vite:data-uri',
buildStart: [Function: buildStart],
resolveId: [Function: resolveId],
load: [Function: load]
},
{
name: 'rollup-plugin-dynamic-import-variables',
transform: [Function: transform]
},
{
name: 'asset-import-meta-url',
transform: [AsyncFunction: transform]
},
{
name: 'vite:import-analysis',
resolveId: [Function: resolveId],
load: [Function: load],
transform: [AsyncFunction: transform],
renderChunk: [Function: renderChunk],
generateBundle: [Function: generateBundle]
},
{
name: 'vite:esbuild-transpile',
renderChunk: [AsyncFunction: renderChunk]
},
{
name: 'vite:terser',
renderChunk: [AsyncFunction: renderChunk],
closeBundle: [Function: closeBundle]
},
{
name: 'vite:reporter',
transform: [Function: transform],
buildEnd: [Function: buildEnd],
renderStart: [Function: renderStart],
renderChunk: [Function: renderChunk],
generateBundle: [Function: generateBundle],
writeBundle: [AsyncFunction: writeBundle]
}
],
configFile: '/Users/maomao/code/vite-project/vite.config.js',
configFileDependencies: [ 'plugin.js', 'vite.config.js' ],
inlineConfig: {
root: undefined,
base: undefined,
mode: undefined,
configFile: undefined,
logLevel: undefined,
clearScreen: undefined,
build: {}
},
root: '/Users/maomao/code/vite-project',
base: '/',
publicDir: '/Users/maomao/code/vite-project/public',
cacheDir: '/Users/maomao/code/vite-project/node_modules/.vite',
command: 'build',
mode: 'production',
isProduction: true,
server: { fs: { strict: undefined, allow: [Array] } },
env: { BASE_URL: '/', MODE: 'production', DEV: false, PROD: true },
assetsInclude: [Function: assetsInclude],
logger: {
hasWarned: false,
info: [Function: info],
warn: [Function: warn],
warnOnce: [Function: warnOnce],
error: [Function: error],
clearScreen: [Function: clearScreen]
},
createResolver: [Function: createResolver],
optimizeDeps: { esbuildOptions: { keepNames: undefined } }
}

其实vite的插件常用的基本都有

https://github.com/vitejs/awesome-vite#plugins

rollUp&vite的插件开发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export default function uglifyImgPlugin () {
return {
name: 'uglifyImg-plugin', // this name will show up in warnings and errors
configResolved(resolvedConfig){
//获取全部的rollUp配置信息
config = resolvedConfig;
},
async closeBundle(){
uglify({
path:path.resolve(__dirname, './'+config.build.outDir)
});
}
};
}

插件开发的api文档

https://rollupjs.org/guide/en/#plugin-development

vite可以使用所有的rollUp的插件,然后vite因为拥有极速冷启动的功能,所以插件的开发会多一些钩子

https://vitejs.dev/guide/api-plugin.html#vite-specific-hooks

[PDF下载]