[万字详解,学习案例] 使用 gulp 去进行自动化构建一个项目
Posted GoldenaArcher
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[万字详解,学习案例] 使用 gulp 去进行自动化构建一个项目相关的知识,希望对你有一定的参考价值。
[万字详解,学习案例] 使用 gulp 去进行自动化构建一个项目
关于 gulp 的更细致的讲解,可以参考上文:逐步详解如何使用 gulp 去进行自动化构建。不看问题也不大,都是手把手地实现,也会带注释。
分析需求
做任何项目或者是实现功能的第一步,都是分析需求
已知项目的结构如下:
其中:
-
public 下面的内容是不需要经过任何处理,可以直接打包上传
-
assets 中是需要处理的文件,包括:
-
fonts,字体文件,需要被压缩
-
images,图片文件,需要压缩
-
scripts,脚本文件,需要压缩
根据需求,可能需要编译(通过 babel)到指定 es 版本
-
styles,样式文件,需要压缩
根据需求,可能需要编译——将 scss/sass 文件编译成为 css 文件
-
-
layout,html 文件,存在模板引擎内容,需要被编译
-
partials,html 文件,存在模板引擎内容,需要被编译
这样就是一个网页项目打包的需求了,有了需求,接下来就一步步的去实现内容即可。
构建项目
准备工作
下载 gulp
第一步先下载并安装 gulp:
yarn add gulp --dev
新建 gulpfile.js
在 gulp 安装完成后,在根目录下新建一个 gulpfile.js,作为 gulp 操作的入口。
所有建立的函数都会做为私有函数的方式进行实现,然后将需要被导出的函数统一通过 module.exports
实现,语法大致如下:
const privateFunc = () => {};
const noExport = () => {};
// 只有这一个函数会被导出
const exportFunc = () => {};
module.exports = { exportFunc };
引入所需要的函数
const { src, dest } = require('gulp');
其中,src
会作为文件的写入流,dest
会作为文件的写出流。
样式编译
样式文件的 io
读取 src/assets/styles
所有的文件,再写出到 dist
目录下同样的位置。
实现代码如下:
// 样式编译
const style = () => {
// base 的作用是为了保留项目结构,不将所有的文件全都写入到 dist 文件夹下
return src('src/assets/styles/*.scss', { base: 'src' }).pipe(dest('dist'));
};
module.exports = {
style,
};
此时在命令行运行样式的编译命令:
gulp-sample> yarn gulp style
写出的结果就生成了:
样式文件的编译
基础的 IO 实现了,现在就开始实现将 scss 转译为 css 的功能,这个功能的视线需要借助插件来完成,所以先安装插件:
gulp-sample> yarn add gulp-sass --dev
介入 gulp-sass 的使用,大部分 gulp-插件 的使用都是通过 pipe 函数,在写入流之前实现。并且大部分的提供的功能是函数,直接调用即可。
// 新增引用
const sass = require('gulp-sass');
// 样式编译
const style = () => {
return (
src('src/assets/styles/*.scss', { base: 'src' })
// 通过 pipe 使用 sass 功能
// outputStyle 用于展开花括号,使得编写更加流程化
.pipe(sass({ outputStyle: 'expanded' }))
.pipe(dest('dist'))
);
};
再次运行编译后得出的项目结构如下:
可以注意到有以下 2 个问题:
-
多余的 scss 文件没有被清理掉
这是因为目前还没有完成清理部分的代码,这个在后面会实现,先暂时手动删除掉多余的文件
-
只有一个 main.css 文件被编译了
这并不是什么错误,根据 Stack Overflow 有个提问:
Why put in front of the file name “” or “” in scss/css?
其中的答复说到:
The _ (underscore) is a partial for scss. That means the stylesheet its going to be imported (@import) to a main stylesheet i.e. styles.scss. The advantage on using partials is that you can use many files to organize your code and everything will be compiled on a single file.
大概意思是说,
_.scss
文件是部分内容,用于分开处理不同的逻辑,使用时通过@import
导入。最终会被编译进一个 css 文件。
所以暂时先将多余的 scss 文件删除掉,编译后获得一个干净的目录:
脚本编译
脚本的任务也需要新增一个函数去执行,这样可以比较好的拆分组件功能。文件的 io 上一步在 样式编译 部分已经实现了,这里就直接先下载所需的插件,然后进行功能的实现。
-
依赖下载
gulp-sample> yarn add gulp-babel --dev gulp-sample> yarn add @babel/core @babel/preset-env --dev
babel 只是提供了一个平台,具体的功能还是需要按照需求下载
-
core 是核心组件
-
preset-env 一个 javascript 的转换插件,安装后可以指定编译为指定的 JavaScript 版本,如 es2021
它包含了最新的 JavaScript 的版本
-
目前来说对于 babel 的配置都是非常简单的,比较复杂的业务需求建议还是通过新建一个 .babelrc 文件,然后在配置文件中实现
-
功能实现
// 新的引用 const babel = require('gulp-babel'); // 脚本编译 const script = () => { return src('src/assets/scripts/*.js', { base: 'src' }) .pipe(babel({ presets: ['@babel/preset-env'] })) .pipe(dest('dist')); }; module.exports = { style, script, };
-
运行脚本并查看结果
# 注意这里要运行的是 script gulp-sample> yarn gulp script
查看结果:
页面模板编译
为了能够实现页面最大的可复用程度,在 HTML 文件中使用了一些模板引擎,如:
单独打开这个页面是无法获得正确被渲染,因此在 gulp 中也要处理一下模板引擎的内容。
-
安装插件
这里使用的模板引擎是 swig,因此需要安装对应的 gulp 插件
gulp-sample> yarn add gulp-swig --dev
-
功能实现
const swig = require('gulp-swig'); const swigData = { pageTitle: 'name', }; // 页面编译 const page = () => { // 只编译根目录下的html,不包含子目录的html,如 layout, partial 等 return ( src('src/*.html', { base: 'src' }) // swigData 是一些需要模板引擎转译的变量 .pipe( swig({ swigData, defaults: { // 默认不缓存文件,可能会存在热更新失败问题 // 为之后的热更新做准备 cache: false, }, }) ) .pipe(dest('dist')) ); }; module.exports = { style, script, page, };
-
运行脚本并查看结果
gulp-sample> yarn gulp page
可以看到所有的 html 已经成功写出
而且内容也已经成功地被编译了:
一些变量名,如 pageTitle 也会被编译成功
至此,主体内容就已经编译完成了
创建组合任务
日常来说,JavaScript, CSS 和 HTML 的编译都是放在一起的,因此可以新建一个组合子任务去将三个任务组合在一起。
实现:
const { src, dest, parallel } = require('gulp');
const compile = parallel(style, script, page);
module.exports = {
compile,
};
这样就可以直接通过运行 compile 去完成组合任务了:
gulp-sample> yarn gulp compile
[03:33:59] Using gulpfile C:\\assignment\\front\\lagoufed-e-task\\part2\\fed-e-task-02-01\\code\\gulp-sample\\gulpfile.js
[03:33:59] Starting 'compile'...
[03:33:59] Starting 'style'...
[03:33:59] Starting 'script'...
[03:33:59] Starting 'page'...
[03:34:01] Finished 'script' after 1.78 s
[03:34:01] Finished 'page' after 1.79 s
[03:34:01] Finished 'style' after 1.79 s
[03:34:01] Finished 'compile' after 1.8 s
Done in 4.24s.
可以看到,在调用了 compile 任务之后,compile 分别调用了 style, script 和 page 去分别进行编译任务
图片和字体文件转换
老规矩,安装插件,实现功能,和运行结果
图片的压缩
-
安装插件
# 图片处理插件 # 这个我最终还是用cnpm下成功了,yarn和npm都不行 gulp-sample> yarn add gulp-imagemin --dev
-
功能实现
const imagemin = require('gulp-imagemin'); // 图片转换 const image = () => { // 这里使用了 ** 通配符,去匹配 images 下的所有文件 return src('src/assets/images/**', { base: 'src' }) .pipe(imagemin()) .pipe(dest('dist')); }; module.exports = { compile, image, };
-
运行结果
gulp-sample> yarn gulp image [09:33:50] Using gulpfile [09:33:50] Starting 'image'... [09:33:52] gulp-imagemin: Minified 2 images (saved 23.1 kB - 26.7%) [09:33:52] Finished 'image' after 1.9
可以看到,图片被压缩了 26.7%,这里的压缩是无损压缩,并不会损坏图片的清晰度,只是会去除一些文件格式的声明
字体的压缩
字体压缩的函数和图片压缩是一样的,这里主要是针对 SVG 进行压缩。
-
功能实现
// 字体转换 const font = () => { return src('src/assets/fonts/**', { base: 'src' }) .pipe(imagemin()) .pipe(dest('dist')); }; module.exports = { compile, image, font, };
-
运行结果
gulp-sample> yarn gulp font [09:35:57] Using gulpfile C:\\assignment\\front\\lagoufed-e-task\\part2\\fed-e-task-02-01\\code\\gulp-sample\\gulpfile.js [09:35:57] Starting 'font'... [09:35:58] gulp-imagemin: Minified 1 image (saved 693 B - 6%) [09:35:58] Finished 'font' after 1.12 s Done in 3.03s.
可以看到,字体也被压缩一些了
最终结果:
可以看到 fonts 和 images 都被拷贝到了 dist 目录中去
这时候只需要做一下最后的清理,将 image 和 font 任务加到 compile 中去即可:
const compile = parallel(style, script, page, image, font);
module.exports = {
compile,
};
其他文件及文件清除
添加其他文件
其他文件指的是将 public 中的文件复制黏贴到 dist 目录下中,并且结合一下 compile,生成一个新的组合任务。
-
功能实现
// cv public下的文件 const extra = () => { return src('public/**', { base: 'public' }).pipe(dest('dist')); }; // 二次组合,分离 compile 和 extra 的业务逻辑 const build = parallel(compile, extra); module.exports = { build, };
-
运行结果
gulp-sample> yarn gulp build [03:56:01] Starting 'build'... [03:56:01] Starting 'extra'... [03:56:01] Starting 'style'... [03:56:01] Starting 'script'... [03:56:01] Starting 'page'... [03:56:01] Starting 'image'... [03:56:01] Starting 'font'... [03:56:05] gulp-imagemin: Minified 2 images (saved 23.1 kB - 26.7%) [03:56:05] Finished 'style' after 4.06 s [03:56:05] Finished 'extra' after 4.07 s [03:56:05] Finished 'script' after 4.07 s [03:56:05] Finished 'page' after 4.07 s [03:56:05] gulp-imagemin: Minified 1 image (saved 693 B - 6%) [03:56:05] Finished 'image' after 4.08 s [03:56:05] Finished 'font' after 4.08 s [03:56:05] Finished 'build' after 4.09 s Done in 5.74s.
最终结果:
可以看到这是后多出了一个 favicon.ico,这是从 public 文件夹下复制进来的。
文件清除
这里也会用到一个额外的依赖包:del,所以依旧老步骤走起。
-
安装依赖包
gulp-sample> yarn add del --dev
-
功能实现
const del = require('del'); // 添加对 series 的引用 const { src, dest, parallel, series } = require('gulp'); // 删除 dist 中的所有内容 const clean = () => { return del(['dist']); }; // 三次组合,先用 series 进行对 dist 的删除,再调用 parallel 去分别执行任务 // parallel 拆分的任务是为了分离 compile 和 extra 的业务逻辑 const build = series(clean, parallel(compile, extra));
-
运行结果
gulp-sample> yarn gulp build [06:34:58] Starting 'build'... [06:34:58] Starting 'clean'... [06:34:58] Finished 'clean' after 25 ms [06:34:58] Starting 'extra'... [06:34:58] Starting 'style'... [06:34:58] Starting 'script'... [06:34:58] Starting 'page'... [06:34:58] Starting 'image'... [06:34:58] Starting 'font'... [06:35:02] gulp-imagemin: Minified 2 images (saved 23.1 kB - 26.7%) [06:35:02] Finished 'style' after 3.74 s [06:35:02] Finished 'extra' after 3.74 s [06:35:02] Finished 'script' after 3.75 s [06:35:02] Finished 'page' after 3.75 s [06:35:02] gulp-imagemin: Minified 1 image (saved 693 B - 6%) [06:35:02] Finished 'image' after 3.76 s [06:35:02] Finished 'font' after 3.77 s [06:35:02] Finished 'build' after 3.8 s Done in 6.17s.
这里截图其实很难说明什么了,不过根据命令行输出可以看出,clean 确实被之行了之后,再去执行打包命令。
自动加载插件
伴随着功能的实现,现在使用的插件也越来越多。如果都是通过 require
去进行调用的话,那么无疑对后期的管理会有一定的难度。
这里又有一个插件,可以提供对插件引入的管理:gulp-load-plugins
-
安装插件依赖
gulp-sample> yarn add gulp-load-plugins --dev
-
功能实现
const loadPlugins = require('gulp-load-plugins'); const plugins = loadPlugins(); // 需要修改下面所有的引用,例如说原本 sass 的地方需要修改为 plugins.sass,以 style 为例 // 样式编译 const style = () => { return ( src('src/assets/styles/*.scss', { base: 'src' }) // 这里原本调用的是 sass,现在掉用的是 plugins.sass .pipe(plugins.sass({ outputStyle: 'expanded' })) .pipe(dest('dist')) ); };
这时候在终端运行一下,效果和之前的是一样的
开发服务器
也就是很多的脚手架会封装的热更新功能,这里依旧会用到另外的插件——browser-sync。
-
安装插件
gulp-sample> yarn add browser-sync --dev
-
功能实现
// 热更新功能 // 非gulp原生功能,所以 plugin 不起作用,还是需要手动引用 const browserSync = require('browser-sync'); const bs = browserSync.create(); const serve = () => { bs.init({ // 根目录,设置的是编译过后的目录 server: { baseDir: 'dist', // 对于项目中,/node_modules/ 是没有办法寻找到正确的路径的 // 这里相对于增添一个路由,去寻找到正确的配置 // 对于 /node_modules 的具体配置,后面会有 routes: { '/node_modules': 'node_modules', }, }, // 监听修改的文件,一单文件被更新,浏览器渲染页面就会同步被渲染 files: 'dist/**', // 启动时不跳出 browser sync 已经连接的消息 notify: false, // 指定端口 port: 2080, }); }; module.exports = { build, serve, };
至此,热更新的功能就实现了,下一步要实现的就是一旦 src 目录下的文件被修改了,那么就会自动部署,即,热部署。
监视变化以及构建优化
监视变化,热部署
这里会使用 gulp 自带的函数,watch 去实现
-
功能实现:
const { src, dest, parallel, series, watch } = require('gulp'); // 监听所有的文件变化,都是之前写过的函数 watch('src/assets/styles/*.scss', style); watch('src/assets/scripts/*.js', script); watch('src/*.html', page); watch('src/assets/images/**', image); watch('src/assets/fonts/**', font); watch('public/**', extra);
-
运行结果
这时候一旦弃用了服务器,然后再修改源代码,就会触发以下的变化:
gulp-sample> yarn gulp serve [10:03:27] Starting 'serve'... [Browsersync] Access URLs: ------------------------------------- Local: http://localhost:2080 External: http://172.28.160.1:2080 ------------------------------------- UI: http://localhost:3001 UI External: http://localhost:3001 ------------------------------------- [Browsersync] Serving files from: dist [Browsersync] Watching files... [10:05:46] Starting 'page'... [10:05:46] Finished 'page' after 237 ms [Browsersync] Reloading Browsers... (buffered 2 events) [10:05:58] Starting 'page'... [10:05:58] Finished 'page' after 142 ms [Browsersync] Reloading Browsers... (buffered 2 events)
对文件的修改会触发对应的任务,从而完成从热更新到热部署的实现。
注:一定要确认 swig 中的 cache 是否设置为 false,如果文件被 swig 缓存了,出于性能的考虑,并不会即时的进行热部署
构建优化
日常开发中对于图片、字体和静态文件的压缩处理需求不是很大,这些更多的是在部署的时候能够减少网页大小而进行的优化作用。对此,可以进行以下的优化:
-
减少图片、字体压缩引起的编译
// 会调用写好的函数,因此要放在函数声明的后面 const serve = () => { // 监听所有的文件变化,都是之前写过的函数 watch('src/assets/styles/*.scss', style); watch('src/assets/scripts/*.js', script); watch('src/*.html', page); // 图片,字体和 public 的更新不会引起重新编译,只会重新加载页面 watch( ["src/assets/images/**", "src/assets/fonts/**", "public/**"], bs.reload ); bs.init({ server: { // baseDir会接受一个数组,具体作用就是在,当文件在较先的index中找不到,会向后顺延,寻找指定的文件 // 这就代表,在开发阶段中,图片和字体可以不需要在dist中打包,可以在src中直接寻找,可以借此提高构建效率 baseDir: ['dist', 'src', 'public'], routes: { '/node_modules': 'node_modules', }, }, ... }); };
-
再构筑一个任务,组合 compile 和 serve
这是因为对于没有打包过的项目,直接运行寻找 dist 下的文件,即使不会报错(dist 不起作用),所获得的文件也是未编译的(跳过 dist 直接到 src 中寻找)。
因此这种情况下可以重新组合一个开发用的命令,按序执行 compile 和 serve 即可
// 开发用的任务 const develop = series(compile, serve); module.exports = { build, develop, };
-
重构一下 build 任务
之前的 build 任务只用 compile 和 extra,现在可以把 image 及 font 也加上去
// 上线用的任务 // 三次组合,先用 series 进行对 dist 的删除,再调用 parallel 去分别执行任务 // parallel 拆分的任务是为了分离 compile 和 其他的 的业务逻辑 const build = series(clean, parallel(compile, image, font, extra));
<
以上是关于[万字详解,学习案例] 使用 gulp 去进行自动化构建一个项目的主要内容,如果未能解决你的问题,请参考以下文章