概述
一、简述
模块2-1主要的学习内容是前端工程化。包括web前端脚手架的开发、web前端项目的自动化构建。
二、主要内容
-
工程化
- 概念:遵循一定的标准和规范,通过工具提高效率降低成本
- 解决的问题
- 传统语言或语法带来的兼容问题。如最新的
ES6
语法、Less
、Sass
、无法兼容,运行环境不能直接支持 - 无法直接使用模块化/组件化
- 重复的机械化工作
- 代码风格无法统一、编码质量得不到保证
- 依赖后端服务接口支持
- 整体依赖后端项目
- 传统语言或语法带来的兼容问题。如最新的
- 在开发流程中的体现
- 创建项目: 使用脚手架工具自动完成基本项目的搭建
- 编码阶段:
- 借助工具自动进行代码格式化和校验,保证代码的风格统一
- 借助编译工具使用新特性,提高开发效果
- 预览/测试阶段:
- 热更新帮助即时查看编码效果
mock
编写假接口,帮助开发业务功能,不用等待后台接口开发sourceMap
帮助定位问题
- 代码提交阶段:
git hooks
在提交之前对项目的质量和风格进行检查,保证代码质量 - 项目部署阶段:自动部署,使用
Jenkins
等工具自动构建并发布
-
web前端脚手架
- 脚手架的本质:脚手架本质是一个
node cli
应用,其作用是创建项目的基础结构,提供项目的基本规范和约定 - 脚手架工具
- 创建项目
- 特定语言的脚手架工具
create-react-app
vue-cli
angular-cli
- 通用的脚手架工具
yeoman
- 特定语言的脚手架工具
- 创建特定类型的文件
Plop
- 创建项目
Yeoman
脚手架工具的使用介绍Yeoman
的基本使用步骤:- 全局安装:
yarn global add yo
- 安装
generator
:Yeoman
需要搭配generator
使用,故此需要全局安装generator
,要生成什么类型的项目,就需要安装对应的generator
。- 要安装的
generator
名字为generator-generatorName
。例如要生成一个node
项目,需要安装一个node
的generator
。yarn global add generator-node
,这里node
就是generator
的名字。
- 要安装的
- 创建文件夹,并运行
yeoman
生成项目。进入创建的文件夹,并使用yo generatorName
命令。例如yo node
,这样就会生成node
一个模块的基础代码。
- 全局安装:
Sub Generator
:生成一些特定的文件,如babel
的配置文件、ESLint
的配置文件 。如通过node generator 的cli Sub Generator
生成cli
命令需要的文件和基本代码。-
运行
generator
的lib Sub Generartor
, 通过yo generatorName:SubGeneratorName
。例如yo node:cli
。注意并不是所有的generator
都有cli
的SubGenerator
,需要通过官方文档查看。
修改package
,并创建了lib/cli.js
文件
其中bin
是cli
文件的声明,meow
是cli
命令的依赖 -
yarn
安装依赖,并通过yarn link/npm link
将本地模块变成全局的模块,这样就能使用全局的命令运行模块。
-
执行
yo-node --help
测试全局模块是否已生效。
-
- 创建
Generator
模块:本质上就是创建npm
模块,即一个Node
模块-
Generator
项目的文件目录结构:
-
创建
Generator
模块的步骤:-
创建一个文件夹,文件夹名字必须是
generator-name
的形式。name
就是要创建的generator
的名字。 -
通过
npm/yarn init
创建一个package.json
文件。其中name
必须是generator-name
的根式,keywords
必须包含yeoman-generator
(如果要发布到yeoman
) ,files
包含generator
要使用的文件和文件夹的名称。{ "name": "generator-test", "version": "1.0.0", "description": "a simple geneartor", "main": "index.js", "scripts": { "plop": "plop" }, "files": [ "generator" ], "keywords": [ "yeoman-generator" ], "bin": "cli.js", "author": "", "license": "ISC", "dependencies": { "plop": "^2.7.4", "yeoman-generator": "^4.12.0" } }
-
安装
yeoman-generator
依赖,并创建generator
模块项目结构generator
有两种项目结构:- 所有文件包含在
generators
文件夹里面yo name
执行的主生成器放在generators/app/index.js
中。- 而
yo name:subGenerator
执行的SubGenerator
放在generators/subGeneratorName/index.js
中。 - 在
package.json
的files
中只需要包含generators
- 所有生成器文件夹放在根目录中
yo name
执行的主生成器放在app/index.js
中。- 而
yo name:subGenerator
执行的SubGenerator
放在subGeneratorName/index.js
中。 - 在
package.json
的files
中只需要包含app
和subGenerator
- 所有文件包含在
-
编写
generator
文件,在文件夹中的index.js
文件中编写-
yeoman-generator
提供了一个基础的generator
,里面提供了一些功能,如文件写入。要创建的generator
需要继承这个generator
并导出。const Generator = require('yeoman-generator'); module.exports = class extends Generator{}
-
Yeoman Generator
在工作时会自动执行导出的类中的方法 -
在生命周期方法中调用父类提供的一些方法实现我们的功能,如文件写入。
write() { // generator 写入文件的时候会调用 writer 方法 // 这里通过 this. 的方式调用父类的一些方法 // this.fs.write( // this.destinationPath('test.txt'), // Math.random().toString() // ); // 模板文件路径 const temp = this.templatePath('temp.html'); // 目标文件路径 const dest = this.destinationPath('test.html'); // 模板数据上下文 const content = this.answers; this.fs.copyTpl(temp, dest, content); }
-
测试:运行
yarn link
将编写的generator link
到全局,然后通过yo generatorName
来运行编写的generator
。
-
-
使用模板创建文件
- 创建并编写模板文件。模板文件支持
EJS
语法 - 调用父类的
fs.copyTpl
方法来写入模板文件内容到目标文件// 模板文件路径 const temp = this.templatePath('temp.html'); // 目标文件路径 const dest = this.destinationPath('test.html'); // 模板数据上下文 const content = this.answers; this.fs.copyTpl(temp, dest, content);
- 创建并编写模板文件。模板文件支持
-
接收用户输入:通过父类的
prompt
方法来实现- 定义
prompting(){}
方法。generator
在询问用户环节时会自动调用此方法 - 在方法中调用父类的
prompt()
方法并返回发出对用户的命令行询问-
prompt
方法 返回一个promise
-
使用
then
方法接收用户输入或将prompting
方法定义为async
方法,并使用await
控制异步流程。async prompting() { this.answers = await this.prompt([ { type: 'input', name: 'name', message: 'Your project name', default: this.appname, // 项目名称。 }, { type: 'confirm', name: 'cool', message: 'Would you link to enable the Cool feature?' } ]); }
-
然后在其他方法中使用获取的
answer
write() { // generator 写入文件的时候会调用 writer 方法 // 这里通过 this. 的方式调用父类的一些方法 // this.fs.write( // this.destinationPath('test.txt'), // Math.random().toString() // ); // 模板文件路径 const temp = this.templatePath('temp.html'); // 目标文件路径 const dest = this.destinationPath('test.html'); // 模板数据上下文 const content = this.answers; this.fs.copyTpl(temp, dest, content); }
-
- 定义
-
-
自定义
vue Generator
来生成Vue
的项目的步骤:- 准备一个基本的项目文件
- 将项目文件中的可变项提取出来,并使用
EJS
等模板语法处理 - 将处理后的文件挨个写入到目标文件。
-
发布自定义的
Generator
git
提交Generator
项目到远端仓库- 安装
publish
yarn/npm publish --registry=https://registry.yarnpkg.com
发布到远端仓库。后面添加--registry
是因为本地是淘宝镜像。- 如果需要同步到
Yeoman
作为一个SubGenerator
, 关键字需要添加yeoman-generator
-
Plop
的使用:创建同类型文件的工具- 基本使用
- 在项目中安装
plop
,npm i plop -d
- 创建
plopfile.js
文件,并编写plop generator
-
plopfile.js
文件导出一个方法,该方法接收一个plop
对象,包含plop.js
提供的所有方法 -
在方法中调用
plop.setGenerator
来定义一个生成器,可以多次调用来定义多个生成器setGenerator(name, option)
: 其中name
是生成器的名字,option
是生成器的配置选项,用来定义用户询问和活动option
包含3个属性-
description
:生成器的描述信息 -
prompts: []
,用户交互问询配置对象prompts: [ { type: 'input', // 用户输入类型 name: 'name', // answer 接收时对应的字段 message: 'Generator name?' // 用户交互时命令行提示信息 } ],
-
actions: []
,定义generator
的活动actions: [ { type: 'add', // 动作类型, add 表示添加文件 path: 'generators/{{name}}/index.js', // 生成文件的路径 templateFile: 'template.hbs' // 模板文件路径 } ]
-
-
运行
plop
的方式-
使用
npm
运行的步骤-
在
package.json
的script
中添加命令行"scripts": { "plop": "plop" },
-
运行命令行
npm run plop generatorName
-
-
使用
yarn
运行: 直接运行yarn plop generatorName
-
-
- 在项目中安装
- 基本使用
- 创建脚手架:即创建一个
node cli
应用-
node cli
文件的头部声明#! /usr/bin/env node
,Unix
和Linux
系统中的头部声明注释,告诉系统文件的执行环境为node
,并且在告诉系统在系统的环境变量中查找node
应用程序的位置。 -
通过命令行交互询问用户问题, 使用
inquire
发起命令行交互 -
根据用户回答结果生成文件
inquirer.prompt([ { type: 'input', name: 'name', message: '请输入项目名称:' } ]).then(answers => { // 获取用户输入的问题答案。根据答案生成文件 // console.log(answers) // 根据 path 获取模板文件所在位置 const tmp = path.resolve(__dirname, '../templates'); // 目标文件位置,即命令行当前位置 const dir = process.cwd(); // fs文件系统读取文件 fs.readdir(tmp, (err, files) => { if(err) throw err; console.log(files.length) files.forEach(f => { // 遍历读取到的文件,并使用ejs模板解析 ejs.renderFile(path.join(tmp, f), answers, (err, result) => { if (err) throw err; // 将结果写入到目标目录 fs.writeFileSync(path.join(dir, f), result); }) }) }) })
-
- 脚手架的本质:脚手架本质是一个
-
前端自动化构建
- 自动化构建:自动将源代码转换成生产代码。开发阶段使用提高效率的语法、规范和标准(
es
,sass
, 模板引擎)浏览器不能直接支持,需要通过转换。 - 自动化构建的工作流
- 按项目结构创建项目目录
- 添加
sass
支持:npm i sass -s
;- 添加
sass
之后可以在命令行使用命令:.node_modules.binsass scssmain.scss cssmian.css
将sass
文件编译成css
文件 - 在
npm scripts
中添加"build": "sass scss/main.scss css/main.css --watch"
, 这样就可以在命令行运行npm run build
来编译文件,--watch
表示监听文件变化,sass
文件变化时会触发编译
- 添加
- 安装
browser-sync
模块在浏览器预览页面效果,npm i browser-sync -s
;- 在
npm scripts
中添加"server": "browser-sync . --files css/*.css"
, 之后可以在命令行运行npm run server
命令,在浏览器打开页面--files css/*.css
表示监听文件变化并同步到浏览器
- 在
- 由于
--watch
会阻塞命令的继续运行,需要有一种方式可以同步运行两个命令行,故此引入npm-run-all
模块,npm i npm-run-all -d
;- 在
npm scripts
中添加"start": "run-p build server"
同时运行build
和server
两个命令 - 命令行运行
npm start
即可启动自动化构建工作
- 在
- 按项目结构创建项目目录
- 常用的自动化构建工具
-
Grunt
:基于临时文件,构建速度慢,插件丰富grunt
的基本使用grunt
代码默认支持同步模式- 安装
grunt
:npm i grunt -D
,开发依赖 - 新建
gruntfile.js
,编写grunt
代码-
gruntfile.js
需要导出一个接收grunt
参数的函数,在函数中注册任务-
任务注册:调用
grunt.registerTask()
方法注册任务。方法接收两个参数:- 第一个参数为
String
类型,表示任务的名称 - 第二个参数可以是 一个函数 或者 任务名称数组
-
参数为一个函数时,运行命令行
grunt taskName
时将会调用函数grunt.registerTask('foo', () => { console.log(grunt.config('foo.bar')); })
-
参数为 任务名称数组 时,运行命令行
grunt taskName
时会依次执行对应的任务grunt.registerTask('default', ['foo', 'bar']);
-
- 第一个参数为
-
运行命令行
grunt taskName
时如果省略taskName
会执行名为default
的任务。grunt.registerTask('default', ['foo', 'bar']);
-
异步任务的注册:
grunt
代码默认支持同步模式, 所以异步任务需要现在回调函数中标识这是个异步任务-
函数中调用
this.async()
标识这个是异步任务,该函数返回一个done
函数。 -
需要在异步任务执行完毕之后调用
done
函数,标识任务结束。grunt.registerTask('task', function() { const done = this.async(); setTimeout(() => { console.log('async hello'); done(false); }, 3000); });
-
-
-
任务的运行通过命令行运行
grunt task
-
npm
: 需要先在package.json
的scripts
中配置命令"grunt": "grunt"
, 然后运行npm run grunt
"scripts": { "grunt": "grunt" },
-
yarn
: 直接运行命令yarn grunt taskName
-
-
任务失败的标记
-
非异步任务的失败,只需要在回调函数中
return false
;grunt.registerTask('foo', () => { console.log(grunt.config('foo.bar')); return false; })
-
任务失败则后续的任务都不会执行
-
异步任务的失败则需要调用 done(false); 时传入 false。
grunt.registerTask('task', function() { const done = this.async(); setTimeout(() => { console.log('async hello'); done(false); }, 3000); });
-
-
任务的配置方法:
-
通过
grunt.initConfig()
方法来初始化配置。方法接收一个对象。grunt.initConfig({ "multi-target": { options: { bar: 123 }, css: { options: { bar: 'css-123', foo: '123' } }, js: 'js-21' }, "clean": { temp: 'tem/app.js', all: 'tem/**' } })
-
配置对象的属性值的获取有两种方式:
grunt.config('foo').bar grunt.config('foo.bar')
-
-
多目标任务:
-
多目标任务的注册:
grunt.registerMultiTask('multi-target', function() {})
-
多目标任务注册时需要在
grunt.initConfig()
的配置中指定一个和任务同名的属性。该属性的值必须为对象形式,值对象中每一个属性即为目标(options
除外),属性名为目标名称grunt.initConfig({ "multi-target": { options: { bar: 123 }, css: 'css-123', js: 'js-21' }, })
-
多目标任务的回调函数会根据配置中的目标个数多次执行,在回调方法中可以通过
this.target
来获取目标名称,this.data
来获取目标数据。// 多目标任务注册 grunt.registerMultiTask('multi-target', function() { console.log(this.target, this.data); });
-
配置对象中的
options
属性作为多目标任务的配置属性,在任务注册回调中通过this.options()
方法来访问配置属性grunt.initConfig({ "multi-target": { options: { bar: 123 }, css: 'css-123', js: 'js-21' }, }) // 多目标任务注册 grunt.registerMultiTask('multi-target', function() { console.log(this.options()); console.log(this.target, this.data); });
-
目标属性的
options
属性会覆盖多目标任务配置中的options
属性。grunt.initConfig({ "multi-target": { options: { bar: 123 }, css: { options: { bar: 'css-123', foo: '123' } }, js: 'js-21' }, "clean": { temp: 'tem/app.js', all: 'tem/**' } }) // 多目标任务注册 grunt.registerMultiTask('multi-target', function() { console.log(this.options()); console.log(this.target, this.data); });
-
目标任务的运行:在任务后面加 “:” 和 目标名称。
-
-
插件的使用:插件是
grunt
的核心- 插件的命名规则:grunt插件的命名统一以
grunt-
加上插件名称的形式。例如grunt-pluginName
。 - 插件的使用:以
grunt-contrib-clean
(文件清除插件) 为例-
安装插件:
npm i grunt-contrib-clean
-
加载插件任务:
grunt.loadNpmTasks('grunt-contrib-clean');
-
配置插件任务:
grunt.initConfig({ pluginName: {} })
grunt.initConfig({ "clean": { temp: 'tem/app.js', all: 'tem/**' } }) // 加载插件中的任务 grunt.loadNpmTasks('grunt-contrib-clean');
-
- 插件的命名规则:grunt插件的命名统一以
-
常用插件:
-
load-grunt-tasks
: 自动加载所有的grunt
插件const loadGruntTasks = require('load-grunt-tasks') // 自动加载grunt插件 loadGruntTasks(grunt)
-
grunt-sass
: 在files
中配置编译的目的文件和源文件sass: { options: { sourceMap: true, implementation: sass }, main: { files: { 'dist/css/app.css': 'scss/app.scss' } } }
-
grunt-babel
: 在files
中配置编译的目的文件和源文件和grunt-sass
类似babel: { options: { sourceMap: true, presets: ['@babel/preset-env'] }, main: { files: { 'dist/js/app.js': 'src/app.js' } } },
-
grunt-contrib-watch
: 监听文件变化,自动执行任务。通过 files 指定监听的文件,task
指定文件变化之后执行的任务。watch: { js: { files: ['src/*.js'], task: ['babel'] }, css: { files: ['scss/*.scss'], task: ['sass'] } },
-
-
- 安装
-
Gulp
:基于内存,速度快,可以同时执行多个任务,插件丰富-
基本使用:
gulp
的函数默认都是异步的- 环境监测:
node 8.11+
npm 5.6+
npx 9.7+
- 安装
gulp
- 新建
gulpfile.js
- 编写
gulpfile.js
-
任务的注册:
-
通过
exports.taskName
导出任务exports.default = series(foo, bar, wsq)
-
通过
task
注册任务task('fff', done => { console.log('ffff'); done(); })
-
-
任务的串行和并行
const { series, parallel, task } = require('gulp');
-
串行:任务依次执行,使用
series
方法实现series(foo, bar, wsq)
-
并行:多个任务同时执行,使用
parallel
方法实现parallel(foo, bar, wsq)
-
-
异步任务的实现方式
-
通过回调函数形式:回调中需要调用传入的
done
函数标志任务结束。-
成功执行的任务
exports.callback = done => { setTimeout(() => { console.log('callback task'); done(); }, 1000); }
-
标记任务错误,需要在
done
函数中传入一个错误对象exports.callback_error = done => { setTimeout(() => { console.log('callback error task'); done(new Error('task error')); }, 1000); }
-
-
通过
promise
实现-
成功执行的任务,返回一个
resolve
状态的promise
exports.promise = () => { console.log('promise task'); return Promise.resolve(); }
-
标记任务错误,返回一个
reject
状态的promise
exports.promise_error = () => { console.log('promise error task'); return Promise.reject(new Error('error promise task')); }
-
-
通过
async/await
实现-
成功执行的任务,await 一个
resolve
状态的promise
const timeoutPromise = (time) => { return new Promise((resolve, reject) => { setTimeout(resolve, time); }) } exports.async = async () => { await timeoutPromise(2000) console.log('async task'); }
-
标记任务错误,await 一个
reject
状态的promise
const timeoutPromise = (time) => { return new Promise((resolve, reject) => { setTimeout(() => { reject(new Error('error async task')) }, time); }) } exports.async = async () => { await timeoutPromise(2000) console.log('async task'); }
-
-
通过
stream
流的形式-
通过流的状态来标志任务是否完成
// stream exports.stream = () => { const readStream = fs.createReadStream('package.json'); const writeStream = fs.createWriteStream('temp.txt'); readStream.pipe(writeStream); return readStream; }
-
其本质是通过监听
stream
的end
状态来确定任务是否完成。exports.stream = done => { const readStream = fs.createReadStream('package.json'); const writeStream = fs.createWriteStream('temp.txt'); readStream.pipe(writeStream); readStream.on('end', () => { done() }) }
-
-
-
错误优先:
gulp
是错误优先的,一旦发生错误,接下来的任务就不会执行。
-
- 环境监测:
-
gulp
构建过程-
读取文件:通过
fs.createReadStream(filePath)
; -
定义转换过程:
callback
接收两个参数,一个错误对象和转换完的输出数据,没有错误则传入null
// 定义文件的转换 const transform = new Transform({ transform: (chunk, encoding, callback) => { const input = chunk.toString(); // 将输入转换为输出,去空格去注释 const output = input.replace(/s+/g, '').replace(//*.+?*\/g, '') callback(null, output) } })
-
输出到指定文件:通过
fs.createWriteStream('destFilePath')
exports.default = () => { // 定义文件读取流 const read = fs.createReadStream('main.css'); // 指定文件输出 const white = fs.createWriteStream('main.min.css'); // 定义文件的转换 const transform = new Transform({ transform: (chunk, encoding, callback) => { const input = chunk.toString(); // 将输入转换为输出,去空格去注释 const output = input.replace(/s+/g, '').replace(//*.+?*\/g, '') callback(null, output) } }) read .pipe(transform) .pipe(white); return read; }
-
-
gulp
文件操作,gulp
提供了两个用于文件操作的函数,src
和dest
src(path)
: 定义文件输入流,支持通配符dest(path)
: 定义文件输入出流,接收一个目录作为参数- 管道:文件流通过管道流向下一个处环境,通过
pipe
方法来表示
const { src, dest } = require('gulp'); const cleanCss = require('gulp-clean-css'); const rename = require('gulp-rename'); exports.default = () => { return src('*.css') .pipe(cleanCss()) .pipe(rename({ extname: '.min.css'})) .pipe(dest('dist')) }
-
一个完整的
gulp
构建流程-
编译
less/sass
文件:通过gulp-less
,安装less / sass
gulp-sass
处理文件流时会自动忽略 “_” 开头的文件
-
编译
js
文件, 通过gulp-babel
,并安装babel
相关的依赖babel, @babel/core @babel/preset-env
-
编译
html
模板文件:通过gulp-swig
,swig
是一种模板语法,也可以使用其他模板语法入ejs
-
swig
模板有缓存机制,在监听页面变化时可能导致看不到最新的结果。通过设置cache
属性来关闭。此外可以通过data
配置属性将数据传入模板。const page = () => { return src('src/**/*.html') .pipe(plugins.swig({ defaults: { cache: false }, data, })) .pipe(dest('dist')) .pipe(bs.reload({ stream: true })) }
-
-
压缩
img
文件:通过gulp-imagemin
, 使用npm
安装失败时使用cnpm
安装。// 复制图片 const image = () => { return src('src/assets/**', { base: 'src' }) .pipe(plugins.imagemin()) .pipe(dest('dist')) .pipe(bs.reload({ stream: true })) }
-
自动导入
gulp
插件: 通过gulp-load-plugins
。 调用插件方法得到插件对象,然后使用 对象. 的方式来使用其他插件。插件名即为属性名。const loadPlugins = require('gulp-load-plugins'); const plugins = loadPlugins(); // 编译css文件 const css = () => { return src('src/**/*.less') .pipe(plugins.less()) .pipe(dest('temp')) }
-
编译之前清理文件,通过
del
,npm i del -D
// 文件清理 const clean = () => { return del(['dist', 'temp']); }
-
开发服务器搭建: 使用
browser-sync
来搭建-
安装
browser-sync
;npm i browser-sync -D
-
导入并创建实例
const bs = require('browser-sync').create();
-
通过 init 初始化服务器配置并启动
bs.init({ // files: 'dist/**', // 可以用 bs.reload 来替代 port: 8099, server: { // baseDir: 'dist', baseDir: ['temp', 'src'], routes: { './node_modules': 'node_modules' } } })
baseDir
:可以是字符串,也可以是字符数组。如果是数组,构建时查找资源文件如果没有从dist
目录中找到,则会依次在后面的目录中去请求资源文件,直到找到为止。routes
:指定相对路径到源文件的映射。即./node_modules
下的资源请求都到根目录下的node_modules
目录下请求
-
监听文件变化及优化
-
监听文件变化通过
gulp
的watch
方法来实现,watch
方法接收两个参数,第一个参数是要监听的文件路径,第二个参数是文件变化之后执行的任务// 监听文件变化 watch('src/**/*.less', css); watch('src/**/*.js', js); watch('src/**/*.html', page); watch('src/assets/**', bs.reload);
也可以通过在任务最后使用
.pipe(bs.reload({stream: true}))
指定在文件变化之后刷新页面// 编译css文件 const css = () => { return src('src/**/*.less') .pipe(plugins.less()) .pipe(dest('temp')) .pipe(bs.reload({stream:true})) }
-
web
服务优化:图片和字体等文件在开发时不需要经过特殊处理,故此可以直接引用源文件,通过baseDir: ['dist', 'src']
进行指定。减少构建次数。图片和字体文件变化是可以通过bs.reload
来刷新浏览器watch('src/assets/**', bs.reload);
-
-
-
项目中的
node_modules
模块的生产代码构建:- 将
node_modules
中的文件打包到一个指定文件中,然后再模板html
中引入。 - 使用
gulp-useref
插件:需要在模板进过模板语法处理之后再处理-
安装依赖
npm i gulp-useref -D
-
在
gulpfile.js
中注册任务,并使用插件处理输出流return src('src/*.html') .pipe(plugins.useref({ searchPath: ['temp', '.'] })) // 指定要处理文件的目录, '.'是为了去查找从 node_modules 中引入的模块。 .pipe(dest('dist'))
-
在
html
模板中编写 构建注释,useref
会将构建注释中引入的资源提取出来放到指定的目标文件中。<!-- build:css css/antd.css --> <link rel="stylesheet" href="/node_modules/antd/dist/antd.dark.css"> <link rel="stylesheet" href="/node_modules/antd/dist/antd.min.css"> <!-- endbuild -->
- 在开始标签
<!-- build:js js/bounds.js -->
中指定构建之后输出的目标文件 <!-- endbuild -->
标志结束。
- 在开始标签
-
- 将
-
对 html 中引入的文件进行压缩:
html
,css
,js
需要在useref
插件处理之后再进行压缩,并输出最终的代码- 使用
gulp-if
对文件进行判断,在使用相应的插件进行压缩处理 - 压缩
html
文件,使用gulp-htmlmin
插件-
htmlmin
插件默认只清除属性中多余的空格,如果需要去除其他内容需要指定属性.pipe(plugins.if(/.html$/, plugins.htmlmin({ // htmlmin 插件默认只清除属性中多余的空格,如果需要去除其他内容需要指定属性 collapseWhitespace: true, // 删除标签之间的空白字符 minifyCSS: true, // 压缩css minifyJS: true, // 压缩js removeComments: true, // 去除注释 })))
-
- 压缩
js
文件, 使用gulp-uglify
插件.pipe(plugins.if(/.js$/, plugins.uglify()))
- 压缩
css
文件, 使用gulp-clean-css
插件.pipe(plugins.if(/.css$/, plugins.cleanCss()))
// 处理 node_modules 中的安装依赖打包 const useref = () => { return src('src/*.html') .pipe(plugins.useref({ searchPath: ['dist', '.'] })) // 指定要处理文件的目录, '.'是为了去查找从 node_modules 中引入的模块。 .pipe(plugins.if(/.html$/, plugins.htmlmin({ // htmlmin 插件默认只清除属性中多余的空格,如果需要去除其他内容需要指定属性 collapseWhitespace: true, // 删除标签之间的空白字符 minifyCSS: true, // 压缩css minifyJS: true, // 压缩js removeComments: true, // 去除注释 }))) .pipe(plugins.if(/.js$/, plugins.uglify())) .pipe(plugins.if(/.css$/, plugins.cleanCss())) .pipe(dest('dist')) }
- 使用
-
构建流程梳理:
useref
对文件处理的前提是html
,css
,js
这些文件都经过了less
、babel
、swig
等编译之后。如果useref
处理文件时从dist
文件读取然后写入到 dist 文件会存在读写冲突。所以需要一个临时文件存储less
、babel
、swig
编译之后的文件,然后useref
再从临时文件中读取,写入到dist
文件。-
修改
less
、babel
、swig
等编译之后的文件目录dist
改为临时目录temp
。// 编译css文件 const css = () => { return src('src/**/*.less') .pipe(plugins.less()) .pipe(dest('temp')) .pipe(bs.reload({stream:true})) } // 编译 js 文件 const js = () => { return src('src/**/*.js') .pipe(plugins.babel({ presets: ['@babel/preset-react', '@babel/preset-env'], plugins: ['@babel/plugin-proposal-class-properties'] })) .pipe(dest('temp')) }
-
browser-sync
插件init
时的文件位置也需要修改// 处理 node_modules 中的安装依赖打包 const useref = () => { return src('src/*.html') .pipe(plugins.useref({ searchPath: ['temp', '.'] })) // 指定要处理文件的目录, '.'是为了去查找从 node_modules 中引入的模块。 .pipe(plugins.if(/.html$/, plugins.htmlmin({ // htmlmin 插件默认只清除属性中多余的空格,如果需要去除其他内容需要指定属性 collapseWhitespace: true, // 删除标签之间的空白字符 minifyCSS: true, // 压缩css minifyJS: true, // 压缩js removeComments: true, // 去除注释 }))) .pipe(plugins.if(/.js$/, plugins.uglify())) .pipe(plugins.if(/.css$/, plugins.cleanCss())) .pipe(dest('dist')) } const server = () => { // 监听文件变化 watch('src/**/*.less', css); watch('src/**/*.js', js); watch('src/**/*.html', page); watch('src/assets/**', bs.reload); bs.init({ // files: 'dist/**', // 可以用 bs.reload 来替代 port: 8099, server: { // baseDir: 'dist', baseDir: ['temp', 'src'], routes: { './node_modules': 'node_modules' } } }) }
-
-
-
-
FIS
:百度推出构建工具,适合初学者使用,使用简单,功能全面,包括自动化工具、性能优化、模块化框架、开发规范、代码部署等。FIS
高度集成,将常用的任务内置在FIS
中。资源定位使其核心能力,编译时将相对路径转化为绝对文件。
-
- 自动化构建:自动将源代码转换成生产代码。开发阶段使用提高效率的语法、规范和标准(
最后,要将经常使用的工作流提取出来,作为一个公共的工作流,然后引入到各个项目中,这样就不用每当工作流需要调整的时候,挨个项目去调整。
在开发好的工作流的基础上对工作流进行封装的开发步骤:
-
公共工作流的提取:新建空白的
node
工程,在lib目录下新建index.js
文件,并将开发的工作流中的gulpfile.js
内容复制到index.js
中,并将devDependencies
中的内容拷贝到node
工程下的dependencies
中 。然后运行yarn/npm install
安装依赖。在此过程中有几点需要注意:-
package.json
中需要通过main
字段来指定文件入口为lib/index.js
。 -
需要在
bin
目录中新建index.js
,并在package.json
中指定bin
字段来配置cli
命令,并以node
模块名为cli
命令名。"main": "lib/index.js", "bin": { "wsq-page": "bin/wsq-page.js" },
-
-
引用公共工作流:现在
node
工程目录中,通过npm link/yarn link
将node
模块发布到本地的全局环境。然后再应用项目目录通过yarn link moduleName
来引用先前link
发布的模块。并在应用项目的gulpfile.js
中直接导出引用模块的内容。module.exports = require('wsq-page')
-
在应用项目的
package.json
中配置scripts
来使用gulp
工作流"scripts": { "compile": "gulp compile", "clean": "gulp clean", "lint": "gulp lint", "serve": "gulp serve", "build": "gulp build", "start": "gulp start", "deploy": "gulp deploy" }
-
提取可变变量到配置文件。对于一些文件路径每个项目可能都不尽相同,因此可以将这些文件路径作为可变配置,以便应用项目进行不同的配置,约定应用项目的根目录可以提供与一个
page.config.js
文件来覆盖默认的配置。配置文件需要导出一个对象,下面是一个配置文件的示例,配置文件可以根据工作流的实际情况进行设计。module.exports = { dist: 'build', temp: 'tp', data: { menus: [ { name: 'Home', icon: 'aperture', link: 'index.html' }, { name: 'Features', link: 'features.html' }, { name: 'About', link: 'about.html' }, { name: 'Contact', link: '#', children: [ { name: 'Twitter', link: 'https://twitter.com/w_zce' }, { name: 'About', link: 'https://weibo.com/zceme' }, { name: 'divider' }, { name: 'About', link: 'https://github.com/zce' } ] } ], pkg: require(`./package.json`), date: new Date() }, }
-
去除应用项目中的
gulpfile.js
,工作流提供cli命令供应用项目直接使用。-
通过分析
node_modules
中的gulp.cmd
,可知运行gulp
命令实际就是node
环境下执行gulpbingulp.js
-
gulpfile.js
不是必须的,命令行运行gulp
命令时可以手动指定gulpfile.js
,对应用项目来说,gulpfile.js
实际上都是指向公共工作流中的入口文件。
-
如果通过命令行运行时指定
gulpfile
为公共工作流中的入口文件,工作目录将发生改变,变为入口文件所在的目录。因此需要通过--cwd
来讲目录指定到当前命令运行的目录。
-
综合上述信息我们可以在公共工作流中提供一个
cli
命令来模拟命令行执行,这样在应用项目中就可以通过命令行来运行工作流中的构建任务,而不必在每个应用项目中添加相同的gulpfile.js
文件。"scripts": { "compile": "wsq-page compile", "clean": "wsq-page clean", "lint": "wsq-page lint", "serve": "wsq-page serve", "build": "wsq-page build", "start": "wsq-page start", "deploy": "wsq-page deploy" },
-
最后
以上就是眯眯眼草莓为你收集整理的拉勾前端高薪就业课程笔记第三弹(模块2-1)的全部内容,希望文章能够帮你解决拉勾前端高薪就业课程笔记第三弹(模块2-1)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复