工程化概述
-
面臨的問題
- ES6+ 兼容問題
- Less、Sass等 不支持
- 模塊化、組件化不支持
- 手動壓縮
- 手動上傳代碼
- 多人開發,難以統一風格
- 等待後端接口完成
- …
-
主要解決的問題
- 傳統語言或語法的弊端
- 無法使用模塊化、組件化
- 重複的機械式工作
- 代碼風格統一、質量保證
- 依賴後端服務接口支持
- 整體依賴後端項目
-
工程化的表現
- 一切重複的工作都應該被自動化
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-QplZ11iN-1593528619196)(./img/1.png)]
- 一切重複的工作都應該被自動化
-
工程化不等於某個工具
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-NNz4A3Mg-1593528619198)(./img/2.png)]- vue-cli
- create-react-app
- angular-cli
- …
-
工程化與node
- 前端工程化由node驅動
腳手架工具開發
-
腳手架的本質
- 創建項目基礎結構、提供項目規範和約定
- 相同的組織結構
- 相同的開發範式
- 相同的模塊依賴
- 相同的工具配置
- 相同的基礎代碼
- 前端腳手架
- 創建項目基礎結構、提供項目規範和約定
-
常用的腳手架工具
- create-react-app
- vue-cli
- angular-cli
- yeoman
- plop(用於創建特定類型文件)
-
yeoman
- 基本使用
- 在全局安裝yo
yarn global add yo
- 安裝對應的generator 如:生成node模塊
yarn global add generator-node
- 通過yo運行generator
yo node
- 在全局安裝yo
- generator
- sub generator
yo node:cli
- sub generator
- 基本使用
-
yeoman 常規使用步驟
- 明確需求
- 找到合適的generator
- 全局安裝找到generator
- 通過yo運行對應的generator
- 通過命令式交互行填寫選項
- 生成所需的項目結構
- 例子: generator-webapp
-
自定義generator (基於yeoman)的實現
- 創建generator本質上就是創建一個npm模塊
- generator基本結構
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-auZ335ee-1593528619200)(./img/3.png)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-9YUN6c2P-1593528619202)(./img/4.png)] - 名稱約定
generator-<name>
- 操作
- 新建目錄generator-sample
- yarn init
- yarn add yeoman-generator
- 按目錄結構創建文件代碼
- npm link
- yo sample
- 根據模塊創建文件代碼
- 接收用戶輸入代碼-prompting
- vue Generator案例
代碼- 注意 使用
yo <name>
之前- 使用npm link
- 安裝yeoman-generator
- 注意 使用
- 發佈generator(實際就是發佈npm模塊)(淘寶鏡像是隻讀的)
- npm public
- yarn public --registry=https://registry.yarnpkg.com
-
plop (小型腳手架工具)
-
腳手架的工作原理
- 實操
- 創建目錄cli-test
- npm init
- 修改package.json 增加
"bin": "cli.js",
- 創建cli.js文件 參考代碼
- npm link
- 命令行運行 cli-test
- 原理
- 通過命令行交互詢問用戶問題
- 根據用戶回答生成文件
- 實操
自動化構建系統
源代碼—(自動化構建)–>生產代碼
1、初體驗
- 創建auto-build目錄
- 創建index.html \ main.scss文件
- 安裝sass
- .\node_modules.bin\sass 查看sass命令
- .\node_modules.bin\sass .\part02\module01\auto-build\main.scss .\part02\module01\auto-build\style.css 編譯scss文件爲css文件
- 安裝browser-sync
- 配置npm script
- 安裝npm-run-all(run-p 命令)
"build":"sass .\\part02\\module01\\auto-build\\main.scss .\\part02\\module01\\auto-build\\style.css",
"serve":"browser-sync ./part02/module01/auto-build",
"start":"run-p build serve"
2、常用的自動化構建工具
- grunt
- 構建速度慢
- gulp
- 同時執行多個任務
- 生產文件存於內存
- fis
- 資源加載、性能優化等集成
3、grunt
- 資源加載、性能優化等集成
- grunt 基本使用
- 安裝grunt
- 目錄下新建gruntfile.js文件參考代碼
yarn grunt foo
執行
- grunt 標記任務失敗參考代碼
- return false
- done(false)異步任務
- grunt的配置方法參考代碼
- grunt多目標任務
// 多目標任務 options不會當做子任務 而是配置 子任務的option會覆蓋上層的options grunt.initConfig({ build:{ options:{ foo:'foo' }, js:1, css:{ options:{ foo:'bar' } } } }) grunt.registerMultiTask('build',function(){ console.log(this.options()); console.log(`target:${this.target},data:${this.data}`); })
- grunt插件的使用
- 安裝grunt-contrib-clean
- grunt.loadNpmTasks(‘grunt-contrib-clean’) 加載
grunt.initConfig({ clean:{ // temp:'temp/app.js', // temp:'temp/*.js', temp:'temp/**' } }) grunt.loadNpmTasks('grunt-contrib-clean')
- grunt常用插件
- grunt-sass
- 依賴sass模塊
- grunt-sass
const sass = require('sass')
module.exports = grunt =>{
grunt.initConfig({
sass:{
options:{
implementation:sass
},
main:{
file:{
'dist/css/main.css':'src/scss/main.scss'
}
}
}
})
grunt.loadNpmTasks('grunt-sass')
}
- grunt-babel
- 依賴@babel/core @babel/preset-env
const loadGruntTasks = require('load-grunt-task') // 自動加載
module.exports = grunt =>{
grunt.initConfig({
babel:{
options:{
presets:['@babel/preset-env']
},
main:{
files:{
'dist/js/app.js':'src/js/app.js'
}
}
}
})
loadGruntTasks(grunt)
}
- grunt-contrib-watch
watch:{
js:{
files:['src/js/*.js'],
tasks:['babel']
},
css:{
files:['src/scss/*.scss'],
tasks:['sass']
}
}
4、gulp
-
基本使用
- 安裝gulp
- 創建入口文件 gulpfile.js文件 參考代碼
yarn gulp <taskName>
-
組合任務
const {series,parallel} = require('gulp')
const task1 = done=>{
setTimeout(()=>{
console.log('task1');
done();
},1000)
}
const task2 = done=>{
setTimeout(()=>{
console.log('task2');
done();
},1000)
}
const task3 = done=>{
setTimeout(()=>{
console.log('task3');
done();
},1000)
}
exports.foo = series(task1,task2,task3); // 串行
exports.bar = parallel(task1,task2,task3);//並行同步執行
- 異步任務
const fs = require('fs')
exports.cb = done=>{
console.log('cb task');
done()
}
exports.cb_error = done =>{
console.log();
done(new Error('task failed!'))
}
exports.promise = ()=>{
console.log('promise task');
return Promise.resolve()
}
exports.promise_error = ()=>{
console.log('promise task');
return Promise.reject(new Error('task failed'))
}
const timeout = time =>{
return new Promise(resolve=>{
setTimeout(resolve,time)
})
}
exports.async = async ()=>{
await timeout(1000)
console.log('async task');
}
// exports.stream = ()=>{
// const readStream = fs.createReadStream('package.json')
// const writeStream = fs.createWriteStream('temp.txt')
// readStream.pipe(writeStream)
// return readStream
// }
exports.stream = done =>{
const readStream = fs.createReadStream('package.json')
const writeStream = fs.createWriteStream('temp.txt')
readStream.pipe(writeStream)
readStream.on('end',()=>{
done()
})
}
- gulp構建過程核心原理
- 讀出文件—轉換—寫入
const fs = require('fs')
const {Transform} = require('stream')
exports.default = ()=>{
// 文件讀取
const read = fs.createReadStream('a.css')
// 文件寫入流
const write = fs.createWriteStream('a.min.css')
const transform = new Transform({
transform:(chunk,encoding,cb)=>{
const input = chunk.toString();
const output = input.replace(/\s+/g,'').replace(/\/\*.+?\*\//g,'')
cb(null,output)
}
})
// 把讀取出來的文件流導入寫入文件流
read.pipe(transform).pipe(write);
return read
}
- 文件操作API + 插件的使用
const {src,dest} = require('gulp')
const cleanCss = require('gulp-clean-css')
const rename = require('gulp-rename')
exports.default = ()=>{
return src('a.css')
.pipe(cleanCss())
.pipe(rename({extname:'.min.css'}))
.pipe(dest('dist'))
}
- 案例:樣式編譯
const { src, dest } = require('gulp')
const sass = require('gulp-sass')
const style = () => {
return src('src/assets/style/*.scss', { base: 'src' })
.pipe(sass({outputStyle:'expanded'}))
.pipe(dest('dist'))
}
module.exports = {
style
}
- 案例:腳本編譯
const babel = require('gulp-babel')
const script = ()=>{
return src('src/assets/scripts/*.js', { base: 'src' })
.pipe(babel({presets:['@babel/preset-env']}))
.pipe(dest('dist'))
}
- 案例:頁面模板編譯
const swig = require('gulp-swig')
const page = ()=>{
return src('src/*.html', { base: 'src' })
.pipe(swig())
.pipe(dest('dist'))
}
- 案例:圖片和字體文件轉換
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/font/**', { base: 'src' })
.pipe(imagemin())
.pipe(dest('dist'))
}
- 案例:其他文件及文件清除
// 主要是拷貝文件
const extra = ()=>{
return src('public/**', { base: 'public' })
.pipe(dest('dist'))
}
// del插件
const del = require('del')
const clean = ()=>{
return del(['dist'])
}
// 構建之前清除
const build = series(clean,parallel(compile,extra))
- 案例:自動加載插件
const gulpLoadPlugins = require('gulp-load-plugins')
const plugins = gulpLoadPlugins()
// 使用時 通過plugins.sass 的方式
const style = () => {
return src('src/assets/style/*.scss', { base: 'src' })
.pipe(plugins.sass({ outputStyle: 'expanded' }))
.pipe(dest('dist'))
}
- 案例:開發服務器 (熱更新)
const browserSync = require('browser-sync')
const bs = browserSync.create()
const server = () => {
bs.init({
server:{
baseDir:'dist'
}
})
}
- 案例:監視變化及構建優化
const { src, dest, parallel ,series,watch} = require('gulp')
const server = () => {
watch('src/assets/style/*.scss',style)
watch('src/assets/scripts/*.js',script)
watch(['src/assets/images/**','src/assets/font/**'],bs.reload)
bs.init({
notify:false,//關閉右上角的tip
port:2080,//端口
open:false,//關閉自動打開瀏覽器
files:'dist/**',// 監聽的目錄
server:{
baseDir:'dist',
routes:{
'/node_modules':'node_modules'
}
}
})
}
- 案例:useref文件引用處理
<!-- 構建註釋 -->
<!-- build:css assets/styles/vendor.css -->
<link rel='stylesheet' href='node_modules/bootstrap/dist/css/bootstrap.css'>
<!-- endbuild-->
const useref = ()=>{
return src('dist/*.html',{base:'dist'})
.pipe(plugins.useref({searchPath:['dist','.']}))
.pipe(dest('dist'))
}
- 案例:文件壓縮
const useref = ()=>{
return src('dist/*.html',{base:'dist'})
.pipe(plugins.useref({searchPath:['dist','.']}))
// html js css
// gulp-html gulp-uglify gulp-clean-css
.pipe(plugins.if(/\.js$/,plugins.uglify()))
.pipe(plugins.if(/\.html$/,plugins.htmlmin({collapseWhitespace:true})))
.pipe(plugins.if(/\.css$/,plugins.cleanCss()))
.pipe(dest('release'))
}
useref打亂了之前的構建流程。
- 案例:重新規劃構建過程
5、封裝自動化構建工作流
-
準備
- 新建一個項目(含遠程倉庫)
- 安裝zce-cli
-
提取gulpfile
- 將gulp-demo中的gulpfile.js 文件內容複製到gxw-pages項目下的lib/index.js(入口文件)中
- 將gulp-demo中的package.json中安裝的依賴複製到gxw-pages的package.json的dependencies
- 刪除gulp-demo項目中的依賴、清空gulpfile.js
- gxw-pages項目通過yarn link 鏈接到本地全局
- 在gulp-demo項目中通過
yarn link "gxw-pages"
鏈接到本項目 - gulp-demo項目中gulpfile.js添加代碼
module.exports = require('gxw-pages')
- gulp-demo項目中安裝一下依賴(原本項目依賴)
- 安裝一下gulp-cl、gulp
- 運行腳本
yarn gulp clean
-
解決模塊中的問題
- gulp-demo項目中創建page.config.js文件(目的是抽離出一些配置信息)
- 在gxw-pages項目中lib/index.js加載配置文件
const cwd = process.cwd();// 返回當前命令行工作目錄 let config = { // default config } try { const loadConfig = require(`${cwd}/pages.config.js`) config = Object.assign({},config,loadConfig) } catch (error) {
}
- 將gxw-pages項目中lib/index.js用到的相關配置改成加載過來的數據
const page = () => {
return src(‘src/*.html’, { base: ‘src’ })
.pipe(swig({ data: config.data }))
.pipe(dest(‘dist’))
}
```
- 抽象路徑配置
- 把寫死的路徑改成可配置的
let config = { // default config build:{ src:'src', dist:'dist', temp:'temp', public:'public', paths:{ styles:'assets/style/*.scss', scripts:'assets/scripts/*.js', pages:'*.html', images:'assets/images/**', fonts:'assets/font/**' } }
}
const style = () => {
return src(config.build.paths.styles, { base: config.build.src,cwd:config.build.src})
.pipe(sass({ outputStyle: ‘expanded’ }))
.pipe(dest(‘dist’))
}
```
- 包裝gulp cli
- gulp-demo項目中gulpfile.js刪除
yarn gulp build --gulpfile .\node_modules\gxw-pages\lib\index.js
- yarn gulp build --gulpfile .\node_modules\gxw-pages\lib\index.js --cwd . (指定當前目錄爲工作目錄)
- 上面的方法傳參太多
- 解決:在gxw-pages項目中提供一個cli
- 在gxw-pages項目新建bin/gxw-pages.js
- 在package.json中配置"bin":“bin/gxw-pages.js”
#!/usr/bin/env node process.argv.push('--cwd') process.argv.push(process.cwd()) process.argv.push('--gulpfile') process.argv.push(require.resolve('..')) require('gulp/bin/gulp')
- gxw-pages clean·
- 發佈使用 gwx-pages
- package.json文件files增加
"files": [ "lib", "bin" ],
- npm publish (要先登錄)
- npm i gwx-pages 在其他項目中使用
6、Fis (高度集成、內置webserver)
- 基本使用
- 安裝fis3
- yarn fis3 release (默認構建任務)
- yarn fis3 release -d dist (指定輸出目錄)
- 配置文件fis-conf.js
- 編譯與壓縮
- yarn fis3 inspect 查看編譯過程
// 安裝 fis-parser-node-sass
fis.match('**/*.scss',{
rExt:'.css',// 修改擴展名
parser:fis.plugin('node-sass'),
optimizer:fis.plugin('clean-css')//壓縮
})
// 安裝
fis.match('**/*.js',{
parser:fis.plugin('babel-6.x'),
optimizer:fis.plugin('uglify-js')
})