技术是为了解决问题存在的
以前那种写demo套模板,已经远远不能满足我们现在开发要求了,所以就有了前端工程化的概念。
既然说了技术是为了解决问题存在的,前端工程化也不例外。
问题:
- 想使用es6+新特性,但是浏览器不兼容
- 想要使用less,sass,PostCss增强css特性,但是运行环境不能直接支持
- 想要模块化的方式提高项目可维护性,但是运行环境不能直接支持
- 部署前需要手动压缩代码及资源文件
- 部署过程需要手动上传代码到服务器
- 多人协同开发,无法硬性统一大家代码风格,从仓库pull代码质量无法保证
- 部分功能开发时需要等待后端服务接口提前完成
主要解决的问题:
工程化的表现:一切以提高效率,降低成本,质量保证为目的的手段都属于工程化。
一切重复的工作都应该被工程化。
创建项目:
- 创建项目结构
- 创建特定类型文件
编码:
- 格式化代码
- 校验代码风格
- 编译/构建/打包
预览/测试:
- web Server/Mock
- Live Reloading/HMR
- Source Map
提交:
- Git Hooks
- Lint-staged
- 持续集成
部署:
- CI/CD
- 自动发布
工程化不是某个工具
工程化是我们项目整体的设计
工具是我们的设计实现所用的东西(webpack,Yeoman)
这几个脚手架,是属于比较成熟的工程化集成(比较固定,针对react,vue,ng)。
工程化的一切都要归功于node,它让前端行业进行了一次工业革命,我们后续说的工具几乎都是基于node开发的。
工程化主要做的就是:
- 脚手架工具开发
- 自动化构建系统
- 模块化打包
- 项目代码规范化
- 自动化部署
之后学习的都是通用的脚手架工具
Yeoman 脚手架基本使用
安装:
yarn global add yo
安装对应的generator(生成器 例如使用node 生成node的项目结构)
yarn global add generator-node
通过yo运行generator
mkdir my-module
yo node
填写相关信息
使用步骤:
- 明确需求
- 找到合适的generatorhttps://yeoman.io/generators/
- 全局范围安装找到的generator
- 通过yo运行对应的generator
- 通过命令行交互填写选项
- 生成你所需要的项目结构
基于Yeoman搭建自己的脚手架(自定义自己的Generator)
Generator本质上就是一个npm模块
首先创建对应(generator-sample <name> 命名规则) 文件夹然后使用:
yarn init
初始化 package.json文件
yarn add yeoman-generator
这个模块提供生成器基类,基类提供了一些工具函数,让我们在创建生成器时更加便捷。
然后根据yeoman目录规范生成generators文件夹,以及默认生成器
然后通过yarn link(npm link )方式 链接到全局范围,使之成为全局模块包
然后创建一个文件夹,然后我们yo sample,sample就是我们刚刚创建好的生成器的名字
执行完之后我们就可以看到 执行完生成的那个文件。
那很多时候我们需要创建的文件有很多,文件内容也相对复杂,那这会我们就可以使用模板去创建文件。
这会我们去创建一个templates目录,然后创建一个bar.html当模板文件。
这是一个模板文件
内部可以使用 EJS 模板标记输出数据
例如:<%= title %>
其他的 EJS 语法也支持
<% if (success) { %>
哈哈哈
<% }%>
然后在导出类里添加prompting方法,Yoman在询问用户环节会自动调用此方法。
prompting () {
// Yeoman 在询问用户环节会自动调用此方法
// 在此方法中可以调用父类的 prompt() 方法发出对用户的命令行询问
return this.prompt([
{
type: 'input',
name: 'name',
message: 'Your project name',
default: this.appname // appname 为项目生成目录文件夹名称
}
])
.then(answers => {
// answers => { name: 'user input value' }
this.answers = answers
})
}
prompt调用发出询问相关信息(具体类型可以看下文档),返回一个promise对象,会把用户输入信息返回,然后存起来方便后续当模板数据上下文使用。
然后在writing(yeoman自动在生成文件阶段调用)方法添加 模板文件路径(通过Generator父类templatePath方法获取),输出目标路径(就是命令行路径父类destinationPath),最后使用(父类里的fs.copyTpl),把文件从一个目录复制到另一个目录,一般是从template目录复制到你所指定的项目目录,不过会事先经过模板引擎的处理,一般用来根据用户输入(保存的模板上下文数据)处理加工文件。api可以直接查看文档。
writing () {
// Yeoman 自动在生成文件阶段调用此方法
// 模板文件路径
const tmpl = this.templatePath('bar.html')
// 输出目标路径
const output = this.destinationPath('bar.html')
// 模板数据上下文
const context = this.answers
this.fs.copyTpl(tmpl, output, context)
}
然后yarn link 到全局,
然后找到一个文件夹和上面一样 yo sample,然后就会发现我们根据模板创建出来的文件了。
vue-generator
然后下面我们就按之前的流程,创建一个带有基础代码的vue.js项目脚手架(除了vue-cli创建自带的基础代码外,我们可以添加一些我们自己想要的模板文件)
首先使用vue-cli创建一个原始项目结构
然后把我们需要使用的文件和文件夹,copy到我们生成器的模板文件夹下:
然后去Generator 的核心入口generators/app/index修改下生成文件阶段的方法,(询问用户环节因为是做测试就不修改了,需要的修改的,可以根据自己需求修改),我们添加一个test.txt 自己文件进去测试。
writing () {
// 把每一个文件都通过模板转换到目标路径
const templates = [
'.browserslistrc',
'.editorconfig',
'.env.development',
'.env.production',
'.eslintrc.js',
'.gitignore',
'babel.config.js',
'package.json',
'postcss.config.js',
'README.md',
'public/favicon.ico',
'public/index.html',
'src/App.vue',
'src/main.js',
'src/router.js',
'src/assets/logo.png',
'src/components/HelloWorld.vue',
'src/store/actions.js',
'src/store/getters.js',
'src/store/index.js',
'src/store/mutations.js',
'src/store/state.js',
'src/utils/request.js',
'src/views/About.vue',
'src/views/Home.vue',
'test.txt'
]
templates.forEach(item => {
// item => 每个文件路径
this.fs.copyTpl(
this.templatePath(item),
this.destinationPath(item),
this.answers
)
})
}
然后我们vue-cli工程里的package.json和 模板html文件做下,从用户输入取上下文的操作如图:
最后如下:
然后我们就靠新做的 vue-generator生成了,符合属于我们自己的项目工程。
也可参考这篇文章yeoman
然后就是发布成一个npm模块
放到git仓库然后 yarn publish输入版本信息上传成为一个npm包
介绍一个小plop脚手架,自动化创建同类型项目文件。
首先安装plop依赖,然后
创建plopfile.js入口,然后编写生成器
module.exports = plop => {
plop.setGenerator('component', {
description: 'application component',
prompts: [
{
type: 'input',
name: 'name',
message: 'component name'
}
],
actions: [
{
type: 'add',
path: 'src/components/{{name}}/{{name}}.js',
templateFile: 'plop-templates/component.js.hbs'
},
{
type: 'add',
path: 'src/components/{{name}}/{{name}}.css',
templateFile: 'plop-templates/component.css.hbs'
},
{
type: 'add',
path: 'src/components/{{name}}/{{name}}.test.js',
templateFile: 'plop-templates/component.test.js.hbs'
}
]
})
}
里面添加描述,询问,然后生成添加文件type,和要生成的目录位置(path),以及模板文件的位置(templateFile)
这是模板文件的样子js举例
最后 yarn plop component执行我们设置的生成器,就会根据我们设置的模板生成对应的文件。
然后我们看下脚手架的实现原理,首先yarn init创建package.json配置文件,然后我们依赖了ejs,inquirer下载。
#!/usr/bin/env node
// Node CLI 应用入口文件必须要有这样的文件头
// 如果是 Linux 或者 macOS 系统下还需要修改此文件的读写权限为 755
// 具体就是通过 chmod 755 cli.js 实现修改
// 脚手架的工作过程:
// 1. 通过命令行交互询问用户问题
// 2. 根据用户回答的结果生成文件
const fs = require('fs')
const path = require('path')
const inquirer = require('inquirer')
const ejs = require('ejs')
inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'Project name?'
}
])
.then(anwsers => {
// console.log(anwsers)
// 根据用户回答的结果生成文件
// 模板目录
const tmplDir = path.join(__dirname, 'templates')
// 目标目录
const destDir = process.cwd()
// 将模板下的文件全部转换到目标目录
fs.readdir(tmplDir, (err, files) => {
if (err) throw err
files.forEach(file => {
// 通过模板引擎渲染文件
ejs.renderFile(path.join(tmplDir, file), anwsers, (err, result) => {
if (err) throw err
// 将结果写入目标文件路径
fs.writeFileSync(path.join(destDir, file), result)
})
})
})
})
然后根据我们设置的模板目录,在里面建上文件(模板采用ejs语法)
然后package.json设置bin属性脚手架入口文件指定
{
"name": "sample-scaffolding",
"version": "0.1.0",
"main": "index.js",
"bin": "cli.js",
"author": "zce <w@zce.me> (https://zce.me)",
"license": "MIT",
"dependencies": {
"ejs": "^2.6.2",
"inquirer": "^7.0.0"
}
}
然后一个最简易版就出来了。
自动化构建
把开发阶段代码转换成生产阶段代码。
类似这种的,还有取出空格,压缩文件。。。等。
常用的构建工具有
npm scripts可以解决一些简单的问题,但是复杂配置的还是需要译者工具来帮我们解决。
grunt基于临时文件构建,速度会慢一点(每一步都会有磁盘读写操作,编译完成过后结果写入临时文件,然后下一个插件再去读取这个文件,处理次数,就会越慢,这个主要做下了解)。
grunt
gulp解决了grunt的问题,基于内存,相对磁盘读写快了很多,支持同时执行多个任务。(更受欢迎)
重点了解下gulp
首先创建package.json,然后创建gulpfile.js入口文件,进行入口编写
// // 导出的函数都会作为 gulp 任务
// exports.foo = () => {
// console.log('foo task working~')
// }
// gulp 的任务函数都是异步的
// 可以通过调用回调函数标识任务完成
exports.foo = done => {
console.log('foo task working~')
done() // 标识任务执行完成
}
// default 是默认任务
// 在运行是可以省略任务名参数
exports.default = done => {
console.log('default task working~')
done()
}
// v4.0 之前需要通过 gulp.task() 方法注册任务
const gulp = require('gulp')
gulp.task('bar', done => {
console.log('bar task working~')
done()
})
这是最基本的,然后多个任务按照顺序执行以及同时执行
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)
}
// 让多个任务按照顺序依次执行
exports.foo = series(task1, task2, task3)
// 让多个任务同时执行
exports.bar = parallel(task1, task2, task3)
gulp也是支持异步的特性的,包括promise
然后我们用node.js实现一下读取转换写入过程
const fs = require('fs')
const { Transform } = require('stream')
exports.default = () => {
// 文件读取流
const readStream = fs.createReadStream('normalize.css')
// 文件写入流
const writeStream = fs.createWriteStream('normalize.min.css')
// 文件转换流
const transformStream = new Transform({
// 核心转换过程
transform: (chunk, encoding, callback) => {
const input = chunk.toString()
const output = input.replace(/\s+/g, '').replace(/\/\*.+?\*\//g, '')
callback(null, output)
}
})
return readStream
.pipe(transformStream) // 转换
.pipe(writeStream) // 写入
}
//最后返回的流默认相当于监听了一个end事件
// exports.stream = done => {
// const read = fs.createReadStream('yarn.lock')
// const write = fs.createWriteStream('a.txt')
// read.pipe(write)
// read.on('end', () => {
// done()
// })
// }
然后gulp自己读取转换写入操作
const { src, dest } = require('gulp')
const cleanCSS = require('gulp-clean-css')
const rename = require('gulp-rename')
exports.default = () => {
return src('src/*.css')
.pipe(cleanCSS())
.pipe(rename({ extname: '.min.css' }))
.pipe(dest('dist'))
}
然后了解这些,我们要实现的就是,对外暴露的就是三个任务:build,develop,clean,打包,开发,和清除
然后我们捋一下流程:
- build流程
1.先执行clean清除任务
2.然后并行(style,script,page模板引擎,这三个任务通过并行parallel函数合成一个compile,然后通过series函数它执行完成后,执行一个压缩这三个代码的操作useref)以及image,font文件,extra(公共文件)的编译输出,转换为浏览器可以识别的格式。
- develop流程
首先执行compile编译style,script,page的输出让浏览器可以认识它们,然后我们创建一个serve任务,启动一个服务去做下文件的访问映射以及热加载更新,这样就完成了开发环境。
- clean清空
然后我们借助插件尝试对一个项目进行打包:
const { src, dest, parallel, series, watch } = require('gulp')
//导出读取src,写入dest,parallel并行,series顺序,watcch监听函数
const del = require('del')//导入清空库的api
const browserSync = require('browser-sync')//导入一个 前端服务工具,用于监听文件热更新
const loadPlugins = require('gulp-load-plugins')//导入一个默认可以获取所有gulp插件的库
const plugins = loadPlugins()//获取所有的gulp插件
const bs = browserSync.create()//创建一个未命名的Browsersync实例
const data = {//html模板文件用到的上下文数据
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()
}
const clean = () => { //gulp的清空文件夹的任务
return del(['dist', 'temp'])
}
const style = () => {//gulp编译 scss文件为css
return src('src/assets/styles/*.scss', { base: 'src' })//读取设置路径下所有的scss文件,设置base路径,从src下查找
.pipe(plugins.sass({ outputStyle: 'expanded' }))//放入sass转换流中 使用expanded展开输出,转换为css
.pipe(dest('temp'))//放入dest的写入流到temp文件夹下
.pipe(bs.reload({ stream: true }))//然后放到更新流中,进行文件更新(或者通过Browsersync实例里init的options的files属性,文件目录监听进行更新)
}
const script = () => {//gulp编译 es6+语法
return src('src/assets/scripts/*.js', { base: 'src' })
.pipe(plugins.babel({ presets: ['@babel/preset-env'] }))//通过babel编译所有最新特性
.pipe(dest('temp'))//和style一样
.pipe(bs.reload({ stream: true }))//和style一样
}
const page = () => {//gulp编译html模板文件
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 = () => {//把public中的文件拿过来,放到dist中
return src('public/**', { base: 'public' })
.pipe(dest('dist'))
}
const serve = () => {//gulp启动一个前端服务的任务
watch('src/assets/styles/*.scss', style)//监听scss文件,发生变化时执行的方法
watch('src/assets/scripts/*.js', script)//监听js文件,发生变化时执行的方法
watch('src/*.html', page)//监听html文件,发生变化时执行的方法
// 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({//启动Browsersync服务
notify: false,//不显示在浏览器中的任何通知。
port: 2080,//端口
// open: false,//是否打开浏览器
// files: 'dist/**',//监听文件下所有文件变化
server: {
baseDir: ['temp', 'src', 'public'],//多个基目录,服务查找文件 顺序, 先从temp下查找,找不到的话,从src下,再找不到从public下查找
routes: {
'/node_modules': 'node_modules' //匹配html中/node_modules路径,匹配到时,更改为相对于当前的工作目录,
//当前项目工程的node_modules中查找
}
}
})
}
const useref = () => {//合并html中部分(js,css为一个)文件的任务,html中有js,和css文件bulid的标识,根据标识合并然后通过if压缩
return src('temp/*.html', { base: 'temp' })//读取html
.pipe(plugins.useref({ searchPath: ['temp', '.'] }))//根据html中标识合并,指定相对于当前工作目录搜索资产文件的位置。可以是字符串或字符串数组。
// html js css
.pipe(plugins.if(/\.js$/, plugins.uglify()))//合完成后,如果,压缩js
.pipe(plugins.if(/\.css$/, plugins.cleanCss()))//完成后如果是css,压缩
.pipe(plugins.if(/\.html$/, plugins.htmlmin({//如果有引入html压缩
collapseWhitespace: true,//压缩html
minifyCSS: true,//压缩css
minifyJS: true//压缩js
})))//
.pipe(dest('dist'))
}
const compile = parallel(style, script, page)//这个几个开发阶段和上线阶段都需要的任务并行执行
// 上线之前执行的任务
const build = series(//顺序执行清除,然后并行执行后四个
clean,
parallel(
series(compile, useref),//先执行style, script, page,进行编译,然后在合并压缩
image,
font,
extra
)
)//最后生成dist
const develop = series(compile, serve)//开发阶段只需要执行这三个的编译,和启动服务就够了
//资源文件的压缩最后上线压缩就够了,开发阶段压缩会影响效率
module.exports = {//最后导出gulp任务
clean,
build,
develop
}
最后把他封装成npm scripts
在packages.json中添加
"scripts": {
"clean": "gulp clean",
"build": "gulp build",
"develop": "gulp develop"
}
然后我们把他做成一个node cli脚手架封装工作流发布
思路是:
- 首先添加脚手架相关信息,添加入口文件(bin/zce-pages.js),添加package.json入口配置(我们主要做的是在执行zce-page命令时它的入口文件执行时使用gulp命令 并且指定一些目录参数,最后命令就相当于 gulp --cwd ...<设置工作目录> --gulpfile ...<设置 gulpfile文件路径> <clean|build|develop>)
- 其次把gulpfile.js配置包在脚手架里,创建(lib/index.js)放入,然后修改package.json里的main入口
- 然后修改package.json里的files,为["lib","bin"]在发布npm包的时候把这俩文件都放上去。
首先添加
bin一般是放脚手架文件我们放了zce-pages.js
然后我们查看gulp命令实际调用了什么
根据我们查看node_modules/bin/gulp.cmd文件
发现调用的是脚手架里gulp,然后我们看到
gulp.js也是调用了脚手架的执行
我们在自己的脚手架文件里,直接引入gulp里的模块,或者引入脚手架,就会让gulp工作
然后我们
#!/usr/bin/env node
process.argv.push('--cwd')//工作目录
process.argv.push(process.cwd())
////做成脚手架的话,命令行通过bin下的cmd 执行 也就是当前工程bin下
//当前目录也就是zce-pages/bin/...这里
process.argv.push('--gulpfile')//gulpfile路径
process.argv.push(require.resolve('..'))
//或者写相对路径require.resolve('../lib/index.js'),不写的话默认..找到上级,然后会默认找package.json里的main 路径做执行
require('gulp/bin/gulp')
这样执行我们这个脚手架,就会让gulp工作了,然后对应写gulp的命令行参数。
然后package.json是这个样子
{
"name": "zce-pages",
"version": "0.2.0",
"description": "static web app workflow",
"keywords": [
"zce-pages",
"zce"
],
"homepage": "https://github.com/zce/zce-pages#readme",
"bugs": {
"url": "https://github.com/zce/zce-pages/issues"
},
"license": "MIT",
"author": "zce <w@zce.me> (https://zce.me)",
"files": [
"lib",
"bin"
],
"main": "lib/index.js",
"bin": "bin/zce-pages.js",
"directories": {
"lib": "lib"
},
"repository": {
"type": "git",
"url": "git+https://github.com/zce/zce-pages.git"
},
"scripts": {
"lint": "standard --fix"
},
"dependencies": {
"@babel/core": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"browser-sync": "^2.26.7",
"del": "^5.1.0",
"gulp": "^4.0.2",
"gulp-babel": "^8.0.0",
"gulp-clean-css": "^4.2.0",
"gulp-htmlmin": "^5.0.1",
"gulp-if": "^3.0.0",
"gulp-imagemin": "^6.1.0",
"gulp-load-plugins": "^2.0.1",
"gulp-sass": "^4.0.2",
"gulp-swig": "^0.9.1",
"gulp-uglify": "^3.0.2",
"gulp-useref": "^3.1.6"
},
"devDependencies": {
"standard": "^13.1.0"
},
"engines": {
"node": ">=6"
}
}
然后我们根据我们上面写的把gulpfile.js的配置更改下,改成可配置的方式放到lib下的index.js里
const { src, dest, parallel, series, watch } = require('gulp')
const del = require('del')
const browserSync = require('browser-sync')
const loadPlugins = require('gulp-load-plugins')
const plugins = loadPlugins()
const bs = browserSync.create()
const cwd = process.cwd()
let config = {
// default config
build: {
src: 'src',
dist: 'dist',
temp: 'temp',
public: 'public',
paths: {
styles: 'assets/styles/*.scss',
scripts: 'assets/scripts/*.js',
pages: '*.html',
images: 'assets/images/**',
fonts: 'assets/fonts/**'
}
}
}
try {
const loadConfig = require(`${cwd}/pages.config.js`)
config = Object.assign({}, config, loadConfig)
} catch (e) {}
const clean = () => {
return del([config.build.dist, config.build.temp])
}
const style = () => {
return src(config.build.paths.styles, { base: config.build.src, cwd: config.build.src })
.pipe(plugins.sass({ outputStyle: 'expanded' }))
.pipe(dest(config.build.temp))
.pipe(bs.reload({ stream: true }))
}
const script = () => {
return src(config.build.paths.scripts, { base: config.build.src, cwd: config.build.src })
.pipe(plugins.babel({ presets: [require('@babel/preset-env')] }))
.pipe(dest(config.build.temp))
.pipe(bs.reload({ stream: true }))
}
const page = () => {
return src(config.build.paths.pages, { base: config.build.src, cwd: config.build.src })
.pipe(plugins.swig({ data: config.data, defaults: { cache: false } }))
.pipe(dest(config.build.temp))
.pipe(bs.reload({ stream: true }))
}
const image = () => {
return src(config.build.paths.images, { base: config.build.src, cwd: config.build.src })
.pipe(plugins.imagemin())
.pipe(dest(config.build.dist))
}
const font = () => {
return src(config.build.paths.fonts, { base: config.build.src, cwd: config.build.src })
.pipe(plugins.imagemin())
.pipe(dest(config.build.dist))
}
const extra = () => {
return src('**', { base: config.build.public, cwd: config.build.public })
.pipe(dest(config.build.dist))
}
const serve = () => {
watch(config.build.paths.styles, { cwd: config.build.src }, style)
watch(config.build.paths.scripts, { cwd: config.build.src }, script)
watch(config.build.paths.pages, { cwd: config.build.src }, page)
// watch('src/assets/images/**', image)
// watch('src/assets/fonts/**', font)
// watch('public/**', extra)
watch([
config.build.paths.images,
config.build.paths.fonts
], { cwd: config.build.src }, bs.reload)
watch('**', { cwd: config.build.public }, bs.reload)
bs.init({
notify: false,
port: 2080,
// open: false,
// files: 'dist/**',
server: {
baseDir: [config.build.temp, config.build.dist, config.build.public],
routes: {
'/node_modules': 'node_modules'
}
}
})
}
const useref = () => {
return src(config.build.paths.pages, { base: config.build.temp, cwd: config.build.temp })
.pipe(plugins.useref({ searchPath: [config.build.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(config.build.dist))
}
const compile = parallel(style, script, page)
// 上线之前执行的任务
const build = series(
clean,
parallel(
series(compile, useref),
image,
font,
extra
)
)
const develop = series(compile, serve)
module.exports = {
clean,
build,
develop
}
然后里面有默认的配置,const loadConfig = require(${cwd}/pages.config.js
) 获取执行命令目录下的配置文件做覆盖。
然后现在我们的一个脚手架就完成了,然后就可以把他发布使用了,后续有需要我们直接迭代这个包就可以了。(自己测试可以把他link到全局)
然后我们项目里下载了这个脚手架就可以通过一下scripts里的命令调用了
//作为npm包发布时依赖项必须放在dependencies不然不会自动下载
我的lp-workflow-cli 已发布欢迎使用
FIS 捆绑套餐,比较典型需求尽可能集成在内部。(大而全)
该文章内容摘抄于拉钩大前端训练营
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。