上一篇:使用 NodeJS 完成一个自定义的小型脚手架工具
一、使用 Gulp 自己创建一个项目自动化构建工具的目的
完成项目开发后,用来对项目进行打包操作
二、用到的第三方插件包
@babel/core: "^7.12.3", // 编译转换js文件,变成es5的js文件
@babel/preset-env: "^7.12.1", // 编译转换js文件,变成es5的js文件
browser-sync: "^2.26.13", // 启动浏览器的服务,打开开发的页面
del: "^6.0.0", // 删除指定的目录和下面的所有文件,返回的是promise对象
gulp: "^4.0.2", // gulp自动化构建项目必备的插件
gulp-babel: "^8.0.0", // 编译转换js文件,变成es5的js文件
gulp-clean-css: "^4.3.0", // 压缩打包时候的css文件
gulp-htmlmin: "^5.0.1", // 压缩打包时候的html文件
gulp-if: "^3.0.0", // 判断流中文件的类型,进行分类打包压缩文件
gulp-imagemin: "^7.1.0", // 把图片进行压缩,减少图片体积
gulp-load-plugins: "^2.0.5", // 自动去加载在package.json文件中下载的以gulp-开头的插件
gulp-sass: "^4.1.0", // 1、编译转换scss文件,变成css文件 // 2、以_开头的scss文件,这个插件会看成是在其他不是以_开头的scss文件中,已经被引用了,所以不会进行编译压缩转换
gulp-swig: "^0.9.1", // 编译转换页面带有模板引擎的html文件文件,变成正常的html文件
gulp-uglify: "^3.0.2", // 压缩打包时候的js文件
gulp-useref: "^5.0.0" // 解决打包后dist目录内html文件中引用的样式、js等不起作用的问题,用useref可以处理html文件中的构建注释,用来线上项目的解决办法中
三、gulp插件中常用的内置方法
const { src, dest, parallel, series, watch } = require('gulp')
src=>文件写入流,dest=>文件写出流
parallel=>组合任务,是并行的,series=>组合任务,是串行的
watch=> 监视文件的变换,从而执行对应的gulp任务
四、gulpfile.js文件内,每一个函数,都是一个任务,向外部暴露对应的任务,实现对应的文件的编译转换工作
五、具体的实现方式
项目整体结构
1、新建一个文件夹,名字qwd-gulp-demo
2、用编辑器打开该文件夹,在编辑器命令行内输入npm init,初始化一个package.json文件
3、在qwd-gulp-demo的根目录下创建一个gulpfile.js文件,作为gulp自动化构建项目的入口文件,文件名字必须是这个
4、安装1个包 npm install --save-dev gulp
5、进入gulpfile.js文件内,编写代码,实现gulp自动化构建项目功能
5-1 分别实现 html文件、js文件、css文件、image图片、字体文件、其他文件、等编译转换工作
代码如下:
const { src, dest } = require('gulp')
const sass = require('gulp-sass')
const babel = require('gulp-babel')
const swig = require('gulp-swig')
const imagemin = require('gulp-imagemin')
const styles = () => {
return src('src/assets/styles/*.scss', { base: 'src' })
.pipe(sass({ outputStyle: "expanded" }))
.pipe(dest('dist'))
}
const scripts = () => {
return src('src/assets/scripts/*.js', { base: 'src' })
.pipe(babel({ presets: ['@babel/env'] }))
.pipe(dest('dist'))
}
const pages = () => {
return src('src/*.html')
.pipe(swig())
.pipe(dest('dist'))
}
const images = () => {
return src('src/assets/images/**', { base: 'src' })
.pipe(imagemin())
.pipe(dest('dist'))
}
const fonts = () => {
return src('src/assets/fonts/**', { base: 'src' })
.pipe(imagemin())
.pipe(dest('dist'))
}
const othersFile = () => {
return src('public/**', { base: 'public' })
.pipe(dest('dist/public'))
}
module.exports = {
styles,
scripts,
pages,
images,
fonts,
othersFile
}
在编辑器工具的命令行内,运行 gulp styles、 gulp scripts、gulp pages、gulp images、gulp fonts、gulp othersFile、可以分别查看对应的编译转换后的文件
但是这样一个一个运行太麻烦,因此可以使用gulp插件内置的parallel,series方法,来把任务组合在一起,这样就可以运行一个命令老实现多个任务的同时处理,代码如下:
把上面代码中的第一行
const { src, dest } = require('gulp')
变成
const { src, dest, parallel, series } = require('gulp')
把上面代码中的末尾部分
module.exports = {
styles,
scripts,
pages,
images,
fonts,
othersFile
}
变成
const combination = parallel(styles, scripts, pages, images, fonts, othersFile)
module.exports = {
combination
}
在编辑器工具的命令行内,运行 gulp combination,就可以查看对应的6个任务,编译转换后的文件,方便太多了
5-2 实现 dist目录以及目录内所有文件的清除操作
目的:可以让开发人员,运行一个命令后,自动把dist目录以及目录内所有文件进行清除
需要安装的插件:npm install --save-dev del 相应代码如下(在5-1代码的基础上,添加的代码):
const del = require('del')
const clean = () => {
// del 返回的promise对象,可以作为gulp任务结束的标记
retrun del(['dist'])
}
module.exports = {
combination,
clean
}
在编辑器工具的命令行内,运行 gulp clean,就可以看到根目录下的dist文件夹已经被删除了
5-3 实现自动加载gulp-开头的插件
原因:gulp-开头的插件,安装太多,一个一个引入,不方便后期的管理
目的:可以让开发人员,不需要在 gulpfile.js文件内引入gulp-开头的插件,就可以直接使用gulp-开头的插件,
需要安装的插件:npm install --save-dev gulp-load-plugins
前提:gulp-开头的插件一定要安装才可以,否则gulp-load-plugins插件也用不了gulp-开头的插件
相应代码如下:
const { src, dest, parallel, series } = require('gulp')
// const sass = require('gulp-sass')
// const babel = require('gulp-babel')
// const swig = require('gulp-swig')
// const imagemin = require('gulp-imagemin')
// 安装了这个插件,上面的4个gulp-开头的插件就不需要引入了,该插件会自动引用的
// gulp-load-plugins引入后返回的是一个函数,需要调用这个函数才可以
const gulpLoadPlugins = require('gulp-load-plugins')()
const del = require('del')
const styles = () => {
return src('src/assets/styles/*.scss', { base: 'src' })
.pipe(gulpLoadPlugins.sass({ outputStyle: "expanded" }))
.pipe(dest('dist'))
}
const scripts = () => {
return src('src/assets/scripts/*.js', { base: 'src' })
.pipe(gulpLoadPlugins.babel({ presets: ['@babel/env'] }))
.pipe(dest('dist'))
}
const pages = () => {
return src('src/*.html')
.pipe(gulpLoadPlugins.swig())
.pipe(dest('dist'))
}
const images = () => {
return src('src/assets/images/**', { base: 'src' })
.pipe(gulpLoadPlugins.imagemin())
.pipe(dest('dist'))
}
const fonts = () => {
return src('src/assets/fonts/**', { base: 'src' })
.pipe(gulpLoadPlugins.imagemin())
.pipe(dest('dist'))
}
const othersFile = () => {
return src('public/**', { base: 'public' })
.pipe(dest('dist/public'))
}
const clean = () => {
// del 返回的promise对象,可以作为gulp任务结束的标记
return del(['dist'])
}
const combination = parallel(styles, scripts, pages, images, fonts, othersFile)
module.exports = {
combination,
clean
}
5-4 实现开发服务器,用来在浏览器显示代码编写后的页面
原因:gulp不能实现预览开发人员,开发出来的页面的效果
目的:可以让开发人员,开发出来的页面在浏览器内显示
需要安装的插件:npm install --save-dev browser-sync
browser-sync网址:browsersync.io/docs/option…
相应代码如下(在5-3 基础的代码基础上,进行添加):
const browserSync = require('browser-sync')
const browser = browserSync.create() // 创建一个热更新的服务器
const serve = () => {
browser.init({
port: 4000,
notify: false,
server: { // 将内置的静态服务器用于基本的HTML / JS / CSS网站
baseDir: ['dist', 'src', 'public'], // // 从来往后依次查找,找到就直接用
// 开发阶段,解决dist目录下,文件内引用的样式、js不起作用的问题
// 线上阶段用useref可以处理html文件中的构建注释,也可以实现压缩
routes: {
'/node_modules': 'node_modules'
}
}
})
}
5-5 实现开发阶段和打包之后的阶段,文件内容一有变换,浏览器就自动更新页面
原因:有了可以让开发的页面显示在浏览器的静态服务器,但是不能实现开发的页面一有变换浏览器就自动刷新页面
目的:可以让开发人员,实现开发的页面一有变换,浏览器就自动刷新页面
实现方法描述:监视gulp中所有任务内的文件变换,有变换就让静态服务器去刷新就可以了
实现方法一:在browser.init({})添加一个配置项:files: 'dist/**'
实现方法二:在serve任务内,添加gulp提供的watch()方法监听任务的变化
相应代码如下:
const { src, dest, parallel, series, watch } = require('gulp')
// 安装了这个插件,gulp-开头的插件就不需要引入了,该插件会自动引用的
// gulp-load-plugins引入后返回的是一个函数,需要调用这个函数才可以
const gulpLoadPlugins = require('gulp-load-plugins')()
const del = require('del')
const browserSync = require('browser-sync')
const browser = browserSync.create() // 创建一个热更新的服务器
const styles = () => {
return src('src/assets/styles/*.scss', { base: 'src' })
.pipe(gulpLoadPlugins.sass({ outputStyle: "expanded" }))
.pipe(dest('dist'))
.pipe(browser.reload({ stream: true }))
}
const scripts = () => {
return src('src/assets/scripts/*.js', { base: 'src' })
.pipe(gulpLoadPlugins.babel({ presets: ['@babel/env'] }))
.pipe(dest('dist'))
.pipe(browser.reload({ stream: true }))
}
const pages = () => {
return src('src/*.html')
.pipe(gulpLoadPlugins.swig())
.pipe(dest('dist'))
.pipe(browser.reload({ stream: true }))
}
const images = () => {
return src('src/assets/images/**', { base: 'src' })
.pipe(gulpLoadPlugins.imagemin())
.pipe(dest('dist'))
}
const fonts = () => {
return src('src/assets/fonts/**', { base: 'src' })
.pipe(gulpLoadPlugins.imagemin())
.pipe(dest('dist'))
}
const othersFile = () => {
return src('public/**', { base: 'public' })
.pipe(dest('dist/public'))
}
const clean = () => {
// del 返回的promise对象,可以作为gulp任务结束的标记
return del(['dist'])
}
const serve = () => {
// 监视开发中文件的变换,从而执行对应的gulp任务
watch('src/assets/styles/*.scss', styles)
watch('src/assets/scripts/*.js', scripts)
watch('src/*.html', pages)
watch('src/assets/images/**', browser.reload)
watch('src/assets/fonts/**', browser.reload)
watch('public/**', browser.reload)
browser.init({
port: 4000,
notify: false,
// 一旦dist文件下的内容改变,浏览器就会自动刷新,显示最新数据,热更新,
// 可以用browser-sync提供的reload()方法来实现
// files: 'dist/**',
server: { // 将内置的静态服务器用于基本的HTML / JS / CSS网站
baseDir: ['dist', 'src', 'public'], // // 从来往后依次查找,找到就直接用
// 开发阶段,解决dist目录下,文件内引用的样式、js不起作用的问题
// 线上阶段用useref可以处理html文件中的构建注释,也可以实现压缩
routes: {
'/node_modules': 'node_modules'
}
}
})
}
const combination = parallel(styles, scripts, pages)
const dev = series(clean, combination, serve)
module.exports = {
clean,
dev,
build
}
5-6 实现项目打包之后的生成的dist目录内,文件内从其他地方引用过来的样式、js不起作用的问题
目的:可以让开发人员,把打包之后的生成的dist目录,文件内从其他地方引用过来的样式、js起作用
需要安装插件:npm install --save-dev gulp-useref
需要安装插件:npm install --save-dev gulp-if
注:虽然在serve任务中,已经实现了可以正确引用,但是那是在开发阶段,在项目打包的时候,是没有用到serve任务的,所以打包后的dist目录,里面的文件内的引用是找不到对应位置的
相应代码如下:
const { src, dest, parallel, series, watch } = require('gulp')
// 安装了这个插件,gulp-开头的插件就不需要引入了,该插件会自动引用的
// gulp-load-plugins引入后返回的是一个函数,需要调用这个函数才可以
const gulpLoadPlugins = require('gulp-load-plugins')()
const del = require('del')
const browserSync = require('browser-sync')
const browser = browserSync.create() // 创建一个热更新的服务器
const styles = () => {
return src('src/assets/styles/*.scss', { base: 'src' })
.pipe(gulpLoadPlugins.sass({ outputStyle: "expanded" }))
.pipe(dest('temp'))
.pipe(browser.reload({ stream: true }))
}
const scripts = () => {
return src('src/assets/scripts/*.js', { base: 'src' })
.pipe(gulpLoadPlugins.babel({ presets: ['@babel/env'] }))
.pipe(dest('temp'))
.pipe(browser.reload({ stream: true }))
}
const pages = () => {
return src('src/*.html')
.pipe(gulpLoadPlugins.swig())
.pipe(dest('temp'))
.pipe(browser.reload({ stream: true }))
}
const images = () => {
return src('src/assets/images/**', { base: 'src' })
.pipe(gulpLoadPlugins.imagemin())
.pipe(dest('dist'))
}
const fonts = () => {
return src('src/assets/fonts/**', { base: 'src' })
.pipe(gulpLoadPlugins.imagemin())
.pipe(dest('dist'))
}
const othersFile = () => {
return src('public/**', { base: 'public' })
.pipe(dest('dist/public'))
}
const clean = () => {
// del 返回的promise对象,可以作为gulp任务结束的标记
return del(['dist', 'temp'])
}
const serve = () => {
// 监视开发中文件的变换,从而执行对应的gulp任务
watch('src/assets/styles/*.scss', styles)
watch('src/assets/scripts/*.js', scripts)
watch('src/*.html', pages)
watch('src/assets/images/**', browser.reload)
watch('src/assets/fonts/**', browser.reload)
watch('public/**', browser.reload)
browser.init({
port: 4000,
notify: false,
// 一旦dist文件下的内容改变,浏览器就会自动刷新,显示最新数据,热更新,
// 可以用browser-sync提供的reload()方法来实现
// files: 'dist/**',
server: { // 将内置的静态服务器用于基本的HTML / JS / CSS网站
baseDir: ['temp', 'src', 'public'], // // 从来往后依次查找,找到就直接用
// 开发阶段,解决dist目录下,文件内引用的样式、js不起作用的问题
// 线上阶段用useref可以处理html文件中的构建注释,也可以实现压缩
routes: {
'/node_modules': 'node_modules'
}
}
})
}
const useref = () => {
// 找到dist目录下的所有的html文件,判断文件内的构建注释里的链接是哪种类型,从而进行处理
// 因为从dist取文件,编制后在放入dist目录,会起冲突,所以需要一个临时目录temp来存储对应的文件
// temp目录是临时目录,打包之后还是要放入dist目录内才行
// 所以需要对所有任务内的入口和出口进行调整
// gulpLoadPlugins.useref({ searchPath: ['temp', '.'] }) 查找的是html文件内的构建注释引用的位置
return src('temp/*.html', { base: 'temp' })
.pipe(gulpLoadPlugins.useref({ searchPath: ['temp', '.'] }))
//gulp-if=>判断文件流中是哪种文件,然后就进行相应的压缩,都是从html中构建注释获取的文件类型
.pipe(gulpLoadPlugins.if(/\.js$/, gulpLoadPlugins.uglify()))
.pipe(gulpLoadPlugins.if(/\.css$/, gulpLoadPlugins.cleanCss()))
.pipe(gulpLoadPlugins.if(/\.html$/, gulpLoadPlugins.htmlmin({ collapseWhitespace: true, minifyCSS: true, minifyJS: true })))
.pipe(dest('dist'))
}
const combination = parallel(styles, scripts, pages)
const dev = series(clean, combination, serve)
const build = series(clean, parallel(series(combination, useref)), images, fonts, othersFile)
module.exports = {
clean,
dev,
build,
combination,
useref
}
5-7 为gulp工具包装一下,让其可以在命令行以运行npm命令的方式来执行gulp任务
代码如下,在package.json文件中进行配置:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"clean": "gulp clean",
"dev": "gulp dev",
"build": "gulp build"
},
在编辑器工具的命令行内,运行 npm run clean 、 npm run dev 、npm run build,发现可以正常执行gulp任务,说明我们的配置没有问题
6、gulp自动化构建项目进行封装,实现复用
6-1 新建一个文件夹:box-qwd-gulp
6-2 运行 npm init 进行初始化
6-3 创建一个常用的..gitignore文件
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
dist/
temp/
6-4 在pack.json文件中添加2个属性
"main": "lib/index.js",
"bin": "bin/box-qwd-gulp-cli.js",
6-5 在box-qwd-gulp文件夹根目下,创建2个目录lib、bin,然后分别创建对应的2个文件index.js、 box-qwd-gulp-cli.js
(1)在index.js文件内添加如下代码:
const { src, dest, parallel, series, watch } = require('gulp')
// 安装了这个插件,gulp-开头的插件就不需要引入了,该插件会自动引用的
// gulp-load-plugins引入后返回的是一个函数,需要调用这个函数才可以
const gulpLoadPlugins = require('gulp-load-plugins')()
const del = require('del')
const browserSync = require('browser-sync')
const browser = browserSync.create() // 创建一个热更新的服务器
const cwd = process.cwd() // 获取到当前代码的工作目录,也就是编辑器内的命令行显示的目录
let config = {
// 默认配置
build: {
src: 'src',
dist: 'dist',
temp: 'temp',
public: 'public',
distPublic: 'dist/public',
paths: {
styles: 'assets/styles/*.scss',
scripts: 'assets/scripts/*.js',
pages: '*.html',
images: 'assets/images/**',
fonts: 'assets/fonts/**',
public: 'public/**'
}
}
}
try {
const config1 = require(`${cwd}/box.qwd.gulp.config.js`)
config = Object.assign({}, config, config1)
} catch (error) {}
const styles = () => {
return src(config.build.paths.styles, {
base: config.build.src,
cwd: config.build.src
})
.pipe(gulpLoadPlugins.sass({ outputStyle: 'expanded' }))
.pipe(dest(config.build.temp))
.pipe(browser.reload({ stream: true }))
}
const scripts = () => {
return (
src(config.build.paths.scripts, {
base: config.build.src,
cwd: config.build.src
})
.pipe(gulpLoadPlugins.babel({ presets: [require('@babel/preset-env')] }))
// .pipe(gulpLoadPlugins.babel({ presets: ['@babel/env'] }))
.pipe(dest(config.build.temp))
.pipe(browser.reload({ stream: true }))
)
}
const pages = () => {
return (
src(config.build.paths.pages, {
base: config.build.src,
cwd: config.build.src
})
// .pipe(gulpLoadPlugins.swig({ data: config.data }))
.pipe(gulpLoadPlugins.swig())
.pipe(dest(config.build.temp))
.pipe(browser.reload({ stream: true }))
)
}
const images = () => {
return src(config.build.paths.images, {
base: config.build.src,
cwd: config.build.src
})
.pipe(gulpLoadPlugins.imagemin())
.pipe(dest(config.build.dist))
}
const fonts = () => {
return src(config.build.paths.fonts, {
base: config.build.src,
cwd: config.build.src
})
.pipe(gulpLoadPlugins.imagemin())
.pipe(dest(config.build.dist))
}
const othersFile = () => {
return src(config.build.paths.public, {
base: config.build.public,
cwd: config.build.public
}).pipe(dest(config.build.distPublic))
// return src('public/**', { base: 'public' })
// .pipe(dest('dist/public'))
}
const clean = () => {
// del 返回的promise对象,可以作为gulp任务结束的标记
return del([config.build.dist, config.build.temp])
}
const serve = () => {
// 监视开发中文件的变换,从而执行对应的gulp任务
watch(config.build.paths.styles, { cwd: config.build.src }, styles)
watch(config.build.paths.scripts, { cwd: config.build.src }, scripts)
watch(config.build.paths.pages, { cwd: config.build.src }, pages)
watch(config.build.paths.images, { cwd: config.build.src }, browser.reload)
watch(config.build.paths.fonts, { cwd: config.build.src }, browser.reload)
watch(config.build.paths.public, { cwd: config.build.public }, browser.reload)
browser.init({
port: 4000,
notify: false,
// 一旦dist文件下的内容改变,浏览器就会自动刷新,显示最新数据,热更新,
// 可以用browser-sync提供的reload()方法来实现
// files: 'dist/**',
server: {
// 将内置的静态服务器用于基本的HTML / JS / CSS网站
baseDir: [config.build.temp, config.build.src, config.build.public], // // 从来往后依次查找,找到就直接用
// 开发阶段,解决dist目录下,文件内引用的样式、js不起作用的问题
// 线上阶段用useref可以处理html文件中的构建注释,也可以实现压缩
routes: {
'/node_modules': 'node_modules'
}
}
})
}
const useref = () => {
// 找到dist目录下的所有的html文件,判断文件内的构建注释里的链接是哪种类型,从而进行处理
// 因为从dist取文件,编制后在放入dist目录,会起冲突,所以需要一个临时目录temp来存储对应的文件
// temp目录是临时目录,打包之后还是要放入dist目录内才行
// 所以需要对所有任务内的入口和出口进行调整
// gulpLoadPlugins.useref({ searchPath: [config.build.temp, '.'] }) 查找的是html文件内的构建注释引用的位置
// return src('temp/*.html', { base: 'temp' })
return (
src(config.build.paths.pages, {
base: config.build.temp,
cwd: config.build.temp
})
.pipe(gulpLoadPlugins.useref({ searchPath: [config.build.temp, '.'] }))
//gulp-if=>判断文件流中是哪种文件,然后就进行相应的压缩,都是从html中构建注释获取的文件类型
.pipe(gulpLoadPlugins.if(/\.js$/, gulpLoadPlugins.uglify()))
.pipe(gulpLoadPlugins.if(/\.css$/, gulpLoadPlugins.cleanCss()))
.pipe(
gulpLoadPlugins.if(
/\.html$/,
gulpLoadPlugins.htmlmin({
collapseWhitespace: true,
minifyCSS: true,
minifyJS: true
})
)
)
.pipe(dest(config.build.dist))
)
}
const combination = parallel(styles, scripts, pages)
const dev = series(clean, combination, serve)
const build = series(
clean,
parallel(series(combination, useref)),
images,
fonts,
othersFile
)
module.exports = {
clean,
dev,
build
}
(2)在box-qwd-gulp-cli.js 文件内添加如下代码:
#!/usr/bin/env node
// #!/usr/bin/env node 作为cli的入口文件
// cwd 的路径
process.argv.push('--cwd')
process.argv.push(process.cwd())
// gulpfile文件路径
process.argv.push('--gulpfile')
process.argv.push(require.resolve('../lib/index.js'))
// process.argv.push(require.resolve('..'))
// process.argv.push('../lib/index.js')
require('gulp/bin/gulp') // 执行node_modules下的gulp/bin/gulp.js文件