项目概述
这是一个7 8年前的项目,页面是使用freemaker做的服务端渲染,构建工具是gulp@3、webpack@3
升级原因
项目中的node-sass需要固定版本的python、c++环境,由于云迁移导致CI环境变化,造成了构建异常。为了降低后续CI环境的运维压力,需要对这个项目的构建过程进行升级,同时这个项目中的angular、cms、vue等较为复杂的子项目已经停止维护了,在生产环境的内容只有一部分,所以升级的事项和影响范围都是比较小的,可以放心升级。
升级核心点是使用sass替换node-sass,它由原来的c++实现换成了node实现,因此需要伴随一系列升级,包括:node版本、webpack、gulp、gulp-sass、sass语法等,以下是升级时的修改细节,以供参考。
改动细节
Node版本
node版本从10.16.0升级为21.1.0
webpack
loader配置
webpack5不再支持loader字符串写法,,需要改写成options,比如
/* ---------------- */
// 这些是废弃的语法
{
loader: "expose-loader?jQuery!expose-loader?$",
}
{
loader: "style-loader!css-loader?importLoaders=1!postcss-loader!sass-loader",
}
{
loader: "imports?this=>window",
}
/* ---------------- */
// 分别需要被替换成
{
loader: "expose-loader",
options: { exposes: ["jQuery", "$"] },
}
{
use: [
"style-loader",
{ loader: "css-loader", options: { importLoaders: 1 } },
"postcss-loader",
{
loader: "sass-loader",
options: { implementation: require("sass") },
},
],
}
{
loader: "imports-loader",
options: {
imports: "this=>window",
},
},
公共包提取
不再支持webpack.optimize.CommonsChunkPlugin
,而是使用optimization.splitChunks
配置,并且公共包不允许从webpack入口处(entry)引入(之前是这样做的)。以下配置是将公共包提取到lib.js文件中。
optimization: {
splitChunks: {
cacheGroups: {
// 创建一个名为 lib 的代码分离组
lib: {
// 提取来自 /node_modules/、handlebars/、jquery/ 的代码
test: /(node_modules)|(handlebars[\/])|(jquery[\/])/,
name: "lib", // 输出的文件名
chunks: "all", // 提取所有的 chunks(异步和同步都提取)
enforce: true, // 强制提取,即使没有引用
},
},
},
}
由于部分代码被分离到lib.js了,它无法受到 exposes: ["jQuery", "$"]
的作用(它是为了让模块中没有显式引入jquery时,也能通过指定全局变量访问到jquery
),因此会出现Uncaught ReferenceError: $ is not defined
的错误。我们可以用插件webpack.ProvidePlugin
来注入$
和jQuery
全局变量,直接移除expose-loader
。
依赖冲突
许多依赖不能在webpack5中使用,因此有以下变化:
删除extract-text-webpack-plugin
,官方已废弃,且项目中并未实际使用,推荐使用mini-css-extract-plugin
抽取css文件
删除style-ext-html-webpack-plugin
,并未实际使用
删除on-build-webpack
,并未实际使用
babel
系列升级为7,子包需要变成@babel/xxx,如babel-preset-env
-> @babel/preset-env
同时presets: ["env", "stage-1"]
需要变成presets: ["@babel/preset-env"]
,去掉stage-1
,已经不推荐使用stage特性。
还需要加上useBuiltIns
、corejs
配置。usage
表示只会在使用到目标环境不支持的代码特性时才会引入polyfills,按需减少打包体积。Babel 默认不会自动引入 polyfills(比如 core-js
或 regenerator-runtime
中的)来填补 JavaScript 环境中缺少的功能。
{
useBuiltIns: "usage",
corejs: 3,
},
由于新旧代码有cjs
、esm
混用的情况,所以还需要在babelrc中配置"sourceType": "unambiguous"
(默认是module
,只允许以esm
导出),否则会遇到Uncaught Error: ES Modules may not assign module.exports or exports.*, Use ESM export syntax, instead
报错。
html-webpack-plugin
@2.30.1 -> @5.6.3
file-loader
@1.1.11 -> @6.2.0
gulp
gulp升级到5版本
gulp-sass升级
为了支持sass
,gulp-sass
需要升级到5以上,它必须指定底层使用哪个sass编译器,而低版本只会强制使用node-sass。
// 指定使用 Dart Sass
const sass = require('gulp-sass')(require('sass'));
const sassOptions = {
// ...options
};
sass(sassOptions)
gulp任务调度重写
之前使用的是run-sequence
调度gulp任务,从gulp@4开始原生支持了串行(gulp.series
)、并发(gulp.parallel
)任务调度,并且gulp@4以上的一些破坏性修改使run-sequence
无法使用了。因此删除了run-sequence
,使用gulp的内置函数重写任务调度流程。
gulp任务声明重写
之前的gulp3使用gulp.task("task_name", taskFunction)
定义任务,然后调度任务的地方使用"task_name"
字符串执行任务,现在4以上的版本已经不推荐甚至不支持了,因为这样写难以追踪和维护。因此所有任务都被改写为函数+模块导出的形式,用到的地方引入模块。
显式模块化引入gulp任务
原先是使用require-dir
库引入tasks
文件夹中的所有任务,这样难以维护,不能明显看出到底有哪些任务。所以停止使用这个库,显式在gulpfile
中引入具体的任务模块。
autoprefixer浏览器配置
不再支持autoprefixer
内部配置受支持的浏览器范围,而是安装browserslist
并使用它的配置。关于这些配置含义,比如> 5%
代表兼容性会考虑到所有份额大于5%的浏览器。
// 之前
autoprefixer({
browsers: [
'last 5 versions',
"Firefox >= 20",
"iOS 7",
"> 5%"
]
})
// 现在npm i -D browserslist,并在package.json中写上配置:
{
"browserslist": [
"last 5 versions",
"Firefox >= 20",
"iOS 7",
"> 5%"
]
}
gulp-rev-all
这是给资源文件名称增加hash的库,不支持gulp5,升级到1.0
gulp-uglify-es
发现项目中混用gulp-uglify
、gulp-uglify-es
压缩代码,所以统一成gulp-uglify-es
,并升级到3.0.0以解决低版本报错。
sass语法
重写了项目中scss所有不兼容的语法
@use
已经不推荐使用@import,它引入的变量是全局的,重名会相互覆盖,没有作用域概念,变量来源也不清晰。使用@use
可以让变量有命名空间,且有作用域,只能在该模块使用。空间名称默认是文件名(会去掉开头_),也可以用as
自定义。
@use './xxx/bar.scss';
@use './xxx/foo.scss' as foo2;
bar.$a;
foo2.$b;
@forward
如果需要从本模块转发其他模块的变量,可以使用这个语法,但是变量的原命名空间会转变成本模块。
// in bar.scss
$a = "#fff";
// in main.scss
@forward './bar.scss';
// in foo.scss
@use './main.scss';
main.$a; // 而不是bar.$a
工具函数空间
不再推荐使用全局的函数,而是从sass提供的命名空间引入,如原来的nth()
应该变成list.nth()
@use 'sass:list';
@mixin sprite-width( $sprite) {
width: list.nth($sprite, 3);
}
项目公共库
项目中使用git subtree
引入的公共包,被私有npm库替代。原先git subtree
的方式,如果不严格按照规范,会在每个使用它的项目里都产生一个独立演化的副本,可能造成维护上的问题,用统一的npm库并通过版本号管理可以缓解维护压力,所以对项目当中的依赖做了全量替换