ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

前端工程化之自动化构建

2021-04-05 09:57:09  阅读:164  来源: 互联网

标签:src const pipe 文件 前端 plugins gulp 自动化 工程化


在前端快速发展的今天,如果不能时刻保持学习就会很快被淘汰。分享一下前端工程化中有关于自动化构建的相关知识,文章有点长,希望对大家有所帮助。每天进步一点点。

一、自动化构建简介

自动化构建就是把我们开发阶段写出来的源代码,自动化的转换成生产环境中可以运行的代码或者程序,这个自动化转换的过程称为自动化构建工作流。

它的作用是尽量脱离运行环境兼容带来的问题,在开发阶段使用一些提高效率的语法,规范和标准,例如,ES6+、Sass、模板引擎等。

二、自动化构建初体验

这里引入 进阶__前端漫漫路 写的 自动化构建 的一片文章

三、Gulp 快速上手

1、Gulp 的基本使用

// 先使用 npm init -y 初始化package.json
// 然后在项目中安装 gulp 的开发依赖
// 在项目根目录添加 .gulpfile 文件,作为 gulp 的入口文件
// .gulpfile 文件用于编写需要 gulp 执行的构建任务
// 编写完成后就可以在命令行终端,使用 yarn gulp 任务名 运行这些构建任务

// 推荐使用导出函数成员的方式,导出构建任务
exports.foo = done => {
	console.log(123)
	done()	// 标识任务完成
}
exports.default = done => {
	console.log('default')
	done()
}

// gulp 4.0 以后仍保留了 gulp.task 创建任务(但不推荐使用)
const gulp = require('gulp')
gulp.task('bar', done => {
	console.log('4.0 bar')
	done()
})

2、Gulp 的组合任务

// 载入两个 构建组合任务的 API
const { series, parallel } = require('gulp')

const task1 = done => {
	setTimeout(() => {
		console.log('task1 working')
		done()
	}, 1000)
}
const task2 = done => {
	setTimeout(() => {
		console.log('task2 working')
		done()
	}, 1000)
}
const task3 = done => {
	setTimeout(() => {
		console.log('task3 working')
		done()
	}, 1000)
}

// 使用 series 构建 串行 的组合任务
// series 是一个函数,接收多个构建任务,并按顺序执行
exports.foo = series(task1, task2, task3)
// 使用 parallel 构建 并行 的组合任务
// parallel 是一个函数,接收多个构建任务,同时执行所有任务
exports.bar = parallel(task1, task2, task3)

3、Gulp 的异步任务

/ Gulp 异步任务的三种方式
// 1、回调函数
exports.callback = done => {
	console.log('task working')
	done()
}
// 错误优先,如果有多个任务,报错后的任务不再执行
exports.callback_err = done => {
	console.log('task working')
	done(new Error('task error'))
}
// 2、支持 promise
exports.promise = () => {
	console.log('task promise')
	return Promise.resolve()
}
// promise 任务失败 [同样失败后的任务不再执行]
exports.promise_err = () => {
	console.log('task promise')
	return Promise.reject(new Error('task promise error'))
}
// async/await [node需要版本8以上]
const timeout = time => {
	return new Promise(resolve => {
		setTimeout(resolve, time)
	})
}
exports.async = async () => {
	await timeout(1000)
	console.log('task async await')
}
// 3、stream
const fs = require('fs')
// exports.stream = () => {
// 	const readStream = fs.createReadStream('package.json')
// 	const writeStream = fs.createWriteStream('temp.txt')
// 	readStream.pipe(writeStream)
// 	return readStream 	// readStream 在 end 的时候完成
// }
// 模拟 gulp 做的事情[只是注册了一个事件去监听任务结束]
exports.stream = done => {
	const readStream = fs.createReadStream('package.json')
	const writeStream = fs.createWriteStream('temp.txt')
	readStream.pipe(writeStream)
	readStream.on('end', () => {
		done()
	})
}

4、Gulp 构建过程核心工作原理

// Gulp 官方定义,The straming build system[基于流的构建系统]
// 核心原理就是:
// 1、通过读取流把我们需要转换的文件读取出来
// 2、经过转换流的转换逻辑,得到我们想要的结果
// 3、再通过写入流把结果写入到指定的写入位置

const fs = require('fs')
const { Transform } = require('stream')
exports.default = () => {
	// 文件读取流
	const read = fs.createReadStream('package.json')
	// 转换流
	const transform = new Transform({
		transform: (chunk, encoding, callback) => {
			// 核心转换过程
			// chunk => 读取流中的读取到的内容 (Buffer)
			const input = chunk.toString()
			const output = input.replace(/\s+/g, '')
			callback(null, output)	// 错误优先,将错误值为 null
		}
	})
	// 文件写入流
	const write = fs.createWriteStream('temp.txt')
	// 把读取出来的文件导入写入文件流
	read
		.pipe(transform)	// 转换
		.pipe(write)	// 写入
	return read
}

5、Gulp 文件操作 API

// Gulp 中 专门用于读取写入文件的 API src 和 dest
// 负责文件加工的转换流一般通过独立的插件提供
// Gulp 工作流程:
// 1、通过src方法创建读取流
// 2、通过插件提供的转换流实现文件加工
// 3、通过dest方法创建写入流,写入到目标文件

const { src, dest } = require('gulp')
// 完成文件压缩转化,需要安装 gulp-clean-css
const cleanCss = require('gulp-clean-css')
// 对文件重命名,需要安装 gulp-rename
const rename = require('gulp-rename')
exports.default = () => {
	return src('src/*.css')	// 读取
		.pipe(cleanCss())	// 文件压缩转化
		.pipe(rename({ extname: '.min.css' }))	// 文件重命名转化
		.pipe(dest('dist'))		// 写入
}

四、Gulp 自动化构建案例

将准备好的资料拷贝到项目中,并安装好基础的依赖。

1、样式编译

gulp-sass 编译 scss 文件

// 导入 读取写入文件的 API src 和 dest
const { src, dest } = require('gulp')
// 需要安装 gulp-sass 插件
const sass = require('gulp-sass')

// 样式编译任务
const style = () => {
	// { base: 'src' }  => 转换时候的基准路径
	// 转换的时候就会将基准路径后面的路径保留下来
	return src('src/assets/styles/*.scss', { base: 'src' })
		// sass 在工作的时候,认为 _ 开头的样式文件是主文件依赖的文件
		// 所以 sass 直接忽略掉这些 _ 开头的样式文件
		// { outputStyle: 'expanded' } => 完全展开的格式生成
		.pipe(sass({ outputStyle: 'expanded' }))
		.pipe(dest('dist'))
}

// 导出任务成员
module.exports = {
	style
}

2、脚本编译

gulp-babel 编译 JS

// 需要安装 gulp-babel 插件
// 同时需要安装 @babel/core @babel/preset-env
const babel = require('gulp-babel')

// 脚本编译任务
const script = () => {
	return src('src/assets/scripts/*.js', { base: 'src' })
		// babel 只是提供一个环境, presets 是 babel 插件的集合
		// 不配置 { presets: ['@babel/preset-env'] } ,转换就不会生效
		.pipe(babel({ presets: ['@babel/preset-env'] }))
		.pipe(dest('dist'))
}

// 导出任务成员
module.exports = {
	style,
	script
}

3、页面模板编译

gulp-swig 处理 HTML 模板文件

// 读取 src, 写入 dest, 组合任务 parallel
const { src, dest, parallel } = require('gulp')
// 需要安装 gulp-swig 插件
const swig = require('gulp-swig')

// 模拟模板页面中需要的动态数据
const data = {
  	menus: [
	    {
	      name: 'Home',
	      icon: 'aperture',
	      link: 'index.html'
	    },
	    {
	      name: 'About',
	      link: 'about.html'
	    },
	    {
	      name: 'Contact',
	      link: '#',
	      children: [
	        {
	          name: 'CSDN',
	          link: 'https://blog.csdn.net/gongye2019'
	        },
	        {
	          name: 'zhihu',
	          link: 'https://www.zhihu.com/people/gong-ye-18-46'
	        },
	        {
	          name: 'Gitee',
	          link: 'https://gitee.com/gongyexj'
	        },
	        {
	          name: 'Github',
	          link: 'https://github.com/gyxj'
	        }
	      ]
	    }
  	],
  	pkg: require('./package.json'),
  	date: new Date()
}

// 页面模板编译任务
const page = () => {
	return src('src/*.html', { base: 'src' })
		.pipe(swig({ data }))
		.pipe(dest('dist'))
}

// 组合多个任务同时执行
const compile = parallel(style, script, page)

// 导出任务成员
module.exports = {
	compile
}

4、图片和文字文件转换

gulp-imagemin 处理图片、 字体

// 需要安装 gulp-imagemin 插件
const imagemin = require('gulp-imagemin')

// 图片转换任务
const image = () => {
	return src('src/assets/images/**', { base: 'src' })
		.pipe(imagemin())
		.pipe(dest('dist'))
}
// 字体文件转换任务
const font = () => {
	return src('src/assets/fonts/**', { base: 'src' })
		.pipe(imagemin())
		.pipe(dest('dist'))
}

// 组合多个任务同时执行
const compile = parallel(style, script, page, image, font)

// 导出任务成员
module.exports = {
	compile
}

5、其他文件及文件清除

del 插件 清除文件

// 需要安装 del 插件,它是一个promise方法
const del = require('del')
// 读取 src, 写入 dest, 组合任务[并行] parallel, 组合任务[串行] series
const { src, dest, parallel, series } = require('gulp')

// 清除文件的方法
const clean = () => {
	return del(['dist'])
}
// 额外的文件,直接拷贝
const extra = () => {
	return src('public/**', { base: 'public' })
		.pipe(dest('dist'))
}

// 通过 series 先执行 clean 任务,在同时执行其他任务
const build = series(clean, parallel(compile, extra))

// 导出任务成员
module.exports = {
	compile,
	build
}

6、自动加载插件

gulp-load-plugins 自动加载插件

// 需要安装 gulp-load-plugins 插件
const loadPlugins = require('gulp-load-plugins')
// 使用 loadPlugins 自动安装插件
const plugins = loadPlugins()
// 需要安装 gulp-sass 插件
// cosnt sass = require('gulp-sass')
// 需要安装 gulp-babel 插件
// 同时需要安装 @babel/core @babel/preset-env
// cosnt babel = require('gulp-babel')
// 需要安装 gulp-swig 插件
// cosnt swig = require('gulp-swig')
// 需要安装 gulp-imagemin 插件
// cosnt imagemin = require('gulp-imagemin')

// 下面代码中用到插件的地方,前面都加上plugins即可,例如:
// 样式编译任务
const style = () => {
	return src('src/assets/styles/*.scss', { base: 'src' })
		.pipe(plugins.sass({ outputStyle: 'expanded' }))
		.pipe(dest('dist'))
}

7、开发服务器

browser-sync 搭建开发服务器

// 需要安装 browser-sync 插件,支持热更新
const browserSync = require('browser-sync')

// 接收 browserSync创建的开发服务器
const bs = browserSync.create()

// 服务任务
const serve = () => {
	bs.init({
		notify: false,	// browserSync 连接提示
		port: 2080,		// 端口
		// open: false,	// 自动打开页面
		files: 'dist/**',	// 监听哪些文件改变后需要更新浏览器
		server: {
			baseDir: 'dist',
			routes: {	// 优先于 baseDir
				'/node_modules': 'node_modules'
			}
		}
	})
}

// 导出任务成员
module.exports = {
	compile,
	build,
	serve
}

8、监视变化及构建优化

watch 监听文件变化

// watch 监视文件变化,决定是否重新执行任务
const { src, dest, parallel, series, watch } = require('gulp')

// 服务任务
const serve = () => {
	// 监视特定的文件来执行特定的任务
	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)
	watch([		// 当图片字体文件或者一些静态文件变化的时候只需要重载一下
		'src/assets/images/**',
		'src/assets/fonts/**',
		'public/**'
	], bs.reload)
	bs.init({
		notify: false,	// browserSync 连接提示
		port: 2080,		// 端口
		// open: false,	// 自动打开页面
		// files: 'dist/**',	// 监听哪些文件改变后需要更新浏览器
		server: {
			baseDir: ['dist', 'src', 'public'],
			routes: {	// 优先于 baseDir
				'/node_modules': 'node_modules'
			}
		}
	})
}
// watch 中的files 监听可以转到每个任务中使用 bs.reload
// 样式编译任务
const style = () => {
	return src('src/assets/styles/*.scss', { base: 'src' })
		.pipe(plugins.sass({ outputStyle: 'expanded' }))
		.pipe(dest('dist'))
		.pipe(bs.reload({ stream: true }))
}
// 脚本编译任务
const script = () => {
	return src('src/assets/scripts/*.js', { base: 'src' })
		.pipe(plugins.babel({ presets: ['@babel/preset-env'] }))
		.pipe(dest('dist'))
		.pipe(bs.reload({ stream: true }))
}
// 页面模板编译任务
const page = () => {
	return src('src/*.html', { base: 'src' })
		.pipe(plugins.swig({ data, defaults: {cache: false}  }))
		.pipe(dest('dist'))
		.pipe(bs.reload({ stream: true }))
}

// 开发环境下图片字体文件不需要压缩,提高执行效率
const compile = parallel(style, script, page)
// 上线之前需要执行的任务
const build = series(clean, parallel(compile, image, font, extra))
// 开发环境需要执行的任务
const dev = series(compile, serve)
// 导出任务成员
module.exports = {
	compile,
	clean,
	build,
	dev
}

9、useref 文件引用处理

gulp-useref 文件引用处理

// useref 插件可以自动处理 html 中的构建注释
// 把引入的第三方的样式文件都引入到一个css文件中
// 把引入的第三方的脚本文件都引入到一个js文件中
// 引入useref 插件
const useref = () => {
	return src('dist/*.html', { base: 'dist' })
		.pipe(plugins.useref({ searchPath: ['dist', '.'] }))
		.pipe(dest('dist'))
}
// 导出任务成员
module.exports = {
	compile,
	clean,
	build,
	dev,
	useref
}

10、文件压缩

gulp-htmlmin, gulp-uglify, gulp-clean-css, gulp-if 进行文件压缩

// 需下载 gulp-htmlmin gulp-uglify gulp-clean-css gulp-if 
// 引入useref 插件
const useref = () => {
	return src('dist/*.html', { base: 'dist' })
		.pipe(plugins.useref({ searchPath: ['dist', '.'] }))
		// 对 html js css 分别进行压缩(需要插件支持)
		.pipe(plugins.if(/\.js$/, plugins.uglify()))
		.pipe(plugins.if(/\.css$/, plugins.cleanCss()))
		.pipe(plugins.if(/\.html$/, plugins.htmlmin({
			collapseWhitespace: true,
			minifyCss: true,
			minifyJs: true
		})))
		// 写入到不同的文件下,防止因为同时读写产生的冲突,文件异常
		.pipe(dest('release'))
}

11、重新规划构建过程

首先将过渡的文件从放在dist改为放到temp,将真正上线的文件放到dist文件夹下

完整源码:

// 需要安装 del 插件,它是一个promise方法
const del = require('del')
// 需要安装 browser-sync 插件,支持热更新
const browserSync = require('browser-sync')

// 读取 src, 写入 dest, 组合任务[并行] parallel, 组合任务[串行] series
// watch 监视文件变化,决定是否重新执行任务
const { src, dest, parallel, series, watch } = require('gulp')
// 需要安装 gulp-load-plugins 插件
const loadPlugins = require('gulp-load-plugins')
// 使用 loadPlugins 自动安装插件
const plugins = loadPlugins()
// 接收创建的开发服务器
const bs = browserSync.create()

// 模拟模板页面中需要的动态数据
const data = {
  	menus: [
	    {
	      name: 'Home',
	      icon: 'aperture',
	      link: 'index.html'
	    },
	    {
	      name: 'About',
	      link: 'about.html'
	    },
	    {
	      name: 'Contact',
	      link: '#',
	      children: [
	        {
	          name: 'CSDN',
	          link: 'https://blog.csdn.net/gongye2019'
	        },
	        {
	          name: 'zhihu',
	          link: 'https://www.zhihu.com/people/gong-ye-18-46'
	        },
	        {
	          name: 'Gitee',
	          link: 'https://gitee.com/gongyexj'
	        },
	        {
	          name: 'Github',
	          link: 'https://github.com/gyxj'
	        }
	      ]
	    }
  	],
  	pkg: require('./package.json'),
  	date: new Date()
}

// 清除文件的方法
const clean = () => {
	return del(['dist', 'temp'])
}

// 样式编译任务
const style = () => {
	// { base: 'src' }  => 转换时候的基准路径
	// 转换的时候就会将基准路径后面的路径保留下来
	return src('src/assets/styles/*.scss', { base: 'src' })
		// sass 在工作的时候,认为 _ 开头的样式文件是主文件依赖的文件
		// 所以 sass 直接忽略掉这些 _ 开头的样式文件
		// { outputStyle: 'expanded' } => 完全展开的格式生成
		.pipe(plugins.sass({ outputStyle: 'expanded' }))
		.pipe(dest('temp'))
		.pipe(bs.reload({ stream: true }))
}

// 脚本编译任务
const script = () => {
	return src('src/assets/scripts/*.js', { base: 'src' })
		// babel 只是提供一个环境, presets 是 babel 插件的集合
		// 不配置 { presets: ['@babel/preset-env'] } ,转换就不会生效
		.pipe(plugins.babel({ presets: ['@babel/preset-env'] }))
		.pipe(dest('temp'))
		.pipe(bs.reload({ stream: true }))
}

// 页面模板编译任务
const page = () => {
	return src('src/*.html', { base: 'src' })
		// // 防止模板缓存导致页面不能及时更新
		.pipe(plugins.swig({ data, defaults: {cache: false}  }))
		.pipe(dest('temp'))
		.pipe(bs.reload({ stream: true }))
}

// 图片转换任务
const image = () => {
	return src('src/assets/images/**', { base: 'src' })
		.pipe(plugins.imagemin())
		.pipe(dest('dist'))
}

// 字体文件转换任务
const font = () => {
	return src('src/assets/fonts/**', { base: 'src' })
		.pipe(plugins.imagemin())
		.pipe(dest('dist'))
}

// 额外的文件,直接拷贝
const extra = () => {
	return src('public/**', { base: 'public' })
		.pipe(dest('dist'))
}

// 服务任务
const serve = () => {
	// 监视特定的文件来执行特定的任务
	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)
	watch([		// 当图片字体文件或者一些静态文件变化的时候只需要重载一下
		'src/assets/images/**',
		'src/assets/fonts/**',
		'public/**'
	], bs.reload)
	bs.init({
		notify: false,	// browserSync 连接提示
		port: 2080,		// 端口
		// open: false,	// 自动打开页面
		// files: 'dist/**',	// 监听哪些文件改变后需要更新浏览器
		server: {
			baseDir: ['temp', 'src', 'public'],
			routes: {	// 优先于 baseDir
				'/node_modules': 'node_modules'
			}
		}
	})
}

// 引入useref 插件
const useref = () => {
	return src('temp/*.html', { base: 'temp' })
		.pipe(plugins.useref({ searchPath: ['temp', '.'] }))
		// 对 html js css 分别进行压缩(需要插件支持)
		.pipe(plugins.if(/\.js$/, plugins.uglify()))
		.pipe(plugins.if(/\.css$/, plugins.cleanCss()))
		.pipe(plugins.if(/\.html$/, plugins.htmlmin({
			collapseWhitespace: true,
			minifyCss: true,
			minifyJs: true
		})))
		.pipe(dest('dist'))
}

// 组合多个任务同时执行
// const compile = parallel(style, script, page, image, font)
// 开发环境下图片字体文件不需要压缩,提高执行效率
const compile = parallel(style, script, page)

// 通过 series 先执行 clean 任务,在同时执行其他任务
// 上线之前需要执行的任务
const build = series(
	clean, 
	parallel(
		series(compile, useref),
		image, 
		font, 
		extra
	)
)

// 开发环境需要执行的任务
const dev = series(compile, serve)

// 导出任务成员
module.exports = {
	clean,
	build,
	serve,
	dev
}

12、补充

将 gulpfile.js 中导出的任务,定义到 package.json 中的 scripts中,方便开发使用

"scripts": {
    "clean": "gulp clean",
    "build": "gulp build",
    "serve": "gulp serve",
    "dev": "gulp dev"
 },

标签:src,const,pipe,文件,前端,plugins,gulp,自动化,工程化
来源: https://blog.csdn.net/gongye2019/article/details/115438328

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有