在写 Purer 主题时用的 Gulp,在 minify JavaScript 的时候遇到了问题。花了挺长时间折腾才找到了解决方法。马后炮的说其实并不难,但是因为 Babel,Gulp 等的版本割裂。网上搜到的方法要么已经过时了或者不全浪费了不少时间。下面用一个 实例 分步骤地记录一下解决方案。

本文写于 2020 年 3 月 16 日

1
2
3
4
5
6
;(async function() {
const $main = document.getElementById('main');
const resp = await fetch('https://v1.jinrishici.com/all.json');
const data = await resp.json();
$main.textContent = data.content;
})()

实例的代码很简单。但是用到了 ES2017 的 async 语法。使用的依赖版本可以在 package.json 中看到就不赘述了。

UglifyJS 只支持 ES5

我们很轻松的就可以写出类似这样的 gulpfile

1
2
3
4
5
6
7
8
9
10
11
12
13
const gulp = require('gulp');
const rename = require('gulp-rename');
const uglify = require('gulp-uglify');
const babel = require('gulp-babel');

gulp.task('js', () => {
return gulp.src('main.js')
.pipe(uglify())
.pipe(rename({suffix: '.min'}))
.pipe(gulp.dest('.'));
})

gulp.task('default', gulp.parallel('js'));

一跑就会发现 UglifyJS 报错。

1
2
[13:15:26] GulpUglifyError: unable to minify JavaScript
Caused by: SyntaxError: Unexpected token: keyword «function», expected: punc «)»

gulp-uglify使用的 UglifyJS 只支持 ES5。

有两个方法解决

  1. 换用 gulp-uglify-es 它使用 terser 支持 ES6+ 语法压缩。
  2. 先用 Babel 降级

如果不用考虑兼容性问题,使用第一种方法就不需要往下看了。如果我早点知道的话就不会花时间去折腾了

我当时很自然的想到用 Babel 降级。

引入 Babel 之后的 gulpfile 长这样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const gulp = require('gulp');
const rename = require('gulp-rename');
const uglify = require('gulp-uglify');
const babel = require('gulp-babel');

gulp.task('js', () => {
return gulp.src('main.js')
.pipe(babel({
presets: ['@babel/preset-env'],
}))
.pipe(uglify())
.pipe(rename({suffix: '.min'}))
.pipe(gulp.dest('.'));
})

gulp.task('default', gulp.parallel('js'));

async 导致的 Babel Polyfill 缺失

gulp build 没有报错。但是假如你也和例子中一样使用了async 的话,运行的时候浏览器会报错。

1
ReferenceError: regeneratorRuntime is not defined

网上找到的信息多半是安装 babel-polyfill, 也有说要安装transform-runtime 等等方法配置 babel。但是 babel-polyfill 已经 Deprecated 了,为了跟得上时代我们还是得跟官方文档,在 babel-preset-env 的官方文档 就能找了正确的配置方法。我们要引入 core-js

安装好 core-js

1
npm install core-js@3 --save

并且更改 gulpfile 中的 babel options

1
2
3
4
5
{
presets: [
['@babel/preset-env', { useBuiltIns: 'usage', corejs: 3 }]
],
}

浏览器 Require 缺失

一通操作下来,浏览器仍然会在运行时报错

1
ReferenceError: require is not defined

说到 require 自然会想到 Browserify。于是依照 文档借助 Babelify 我们很自然的写出类似这样的 gulpfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const gulp = require('gulp');
const rename = require('gulp-rename');
const uglify = require('gulp-uglify');
const babel = require('gulp-babel');
const browserify = require('browserify');

gulp.task('js', () => {
return browserify("main.js")
.transform("babelify", {
presets: [
['@babel/preset-env', { useBuiltIns: 'usage', corejs: 3 }]
],
})
.bundle()
.pipe(uglify())
.pipe(rename({suffix: '.min'}))
.pipe(gulp.dest('.'));
})

gulp.task('default', gulp.parallel('js'));

build 一下遇到了这样奇怪的错误,

1
TypeError: file.isNull is not a function

Vinyl stream 不兼容

问题出在 Browserify 的流和 Gulp 的流不兼容。Gulp 的流使用的是 Vinyl, 而 browserify 使用的 node fs 的流。我们需要额外做一些转换。

安装相关的包。

1
2
npm i -D vinyl-source-stream
npm i -D vinyl-buffer

最终 gulpfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const gulp = require('gulp');
const rename = require('gulp-rename');
const uglify = require('gulp-uglify');
const babel = require('gulp-babel');
const browserify = require('browserify');
const source = require('vinyl-source-stream');
const buffer = require('vinyl-buffer');

gulp.task('js', () => {
return browserify("main.js")
.transform("babelify", {
presets: [
['@babel/preset-env', { useBuiltIns: 'usage', corejs: 3 }]
],
})
.bundle()
.pipe(source('main.js'))
.pipe(buffer())
.pipe(uglify())
.pipe(rename({suffix: '.min'}))
.pipe(gulp.dest('.'));
})

gulp.task('default', gulp.parallel('js'));

总结

至此这么一小段 js 终于被编译成可以运行的 minify 的 ES5 了。可是这么一通操作下来引入了 core-js 做 polyfill 体积不减反增 maxify

1
2
4.0K    main.js
36K main.min.js

所以说如果不考虑兼容性就直接用 gulp-uglify-es 好了。

前端的构建工具大版本总是不兼容,网上的信息也很多已经过时了。这篇文章估计在不久之后也会过时的。不得不说跟上时代最好的方法还是官方文档呀。

实例放在了 GitHub,各个步骤都对应的分支。
我们还可以借助 github-history 来看 gulpfile 的变化