前端工程化(上2-1)

Charon

技术是为了解决问题存在的

以前那种写demo套模板,已经远远不能满足我们现在开发要求了,所以就有了前端工程化的概念。

image.png

既然说了技术是为了解决问题存在的,前端工程化也不例外。
问题:

  • 想使用es6+新特性,但是浏览器不兼容
  • 想要使用less,sass,PostCss增强css特性,但是运行环境不能直接支持
  • 想要模块化的方式提高项目可维护性,但是运行环境不能直接支持
  • 部署前需要手动压缩代码及资源文件
  • 部署过程需要手动上传代码到服务器
  • 多人协同开发,无法硬性统一大家代码风格,从仓库pull代码质量无法保证
  • 部分功能开发时需要等待后端服务接口提前完成
主要解决的问题:

image.png

工程化的表现:一切以提高效率,降低成本,质量保证为目的的手段都属于工程化。
一切重复的工作都应该被工程化。
image.png

创建项目:

  • 创建项目结构
  • 创建特定类型文件

编码:

  • 格式化代码
  • 校验代码风格
  • 编译/构建/打包

预览/测试:

  • web Server/Mock
  • Live Reloading/HMR
  • Source Map

提交:

  • Git Hooks
  • Lint-staged
  • 持续集成

部署:

  • CI/CD
  • 自动发布
工程化不是某个工具

image.png
工程化是我们项目整体的设计

工具是我们的设计实现所用的东西(webpack,Yeoman)

image.png
这几个脚手架,是属于比较成熟的工程化集成(比较固定,针对react,vue,ng)。

工程化的一切都要归功于node,它让前端行业进行了一次工业革命,我们后续说的工具几乎都是基于node开发的。

工程化主要做的就是:

  • 脚手架工具开发
  • 自动化构建系统
  • 模块化打包
  • 项目代码规范化
  • 自动化部署

之后学习的都是通用的脚手架工具

Yeoman 脚手架基本使用

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模块
image.png

首先创建对应(generator-sample <name> 命名规则) 文件夹然后使用:

yarn init 

初始化 package.json文件

yarn add yeoman-generator

这个模块提供生成器基类,基类提供了一些工具函数,让我们在创建生成器时更加便捷。

然后根据yeoman目录规范生成generators文件夹,以及默认生成器
image.png
image.png
然后通过yarn link(npm link )方式 链接到全局范围,使之成为全局模块包

然后创建一个文件夹,然后我们yo sample,sample就是我们刚刚创建好的生成器的名字
image.png

执行完之后我们就可以看到 执行完生成的那个文件。

那很多时候我们需要创建的文件有很多,文件内容也相对复杂,那这会我们就可以使用模板去创建文件。

这会我们去创建一个templates目录,然后创建一个bar.html当模板文件。
image.png
image.png
这是一个模板文件
内部可以使用 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)
  }

image.png

然后yarn link 到全局,
然后找到一个文件夹和上面一样 yo sample,然后就会发现我们根据模板创建出来的文件了。

vue-generator

然后下面我们就按之前的流程,创建一个带有基础代码的vue.js项目脚手架(除了vue-cli创建自带的基础代码外,我们可以添加一些我们自己想要的模板文件)
首先使用vue-cli创建一个原始项目结构
image.png

然后把我们需要使用的文件和文件夹,copy到我们生成器的模板文件夹下:
image.png
然后去Generator 的核心入口generators/app/index修改下生成文件阶段的方法,(询问用户环节因为是做测试就不修改了,需要的修改的,可以根据自己需求修改),我们添加一个test.txt 自己文件进去测试。
image.png

 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文件做下,从用户输入取上下文的操作如图:
image.png
image.png
最后如下:
image.png
然后我们就靠新做的 vue-generator生成了,符合属于我们自己的项目工程。
也可参考这篇文章yeoman

然后就是发布成一个npm模块
放到git仓库然后 yarn publish输入版本信息上传成为一个npm包

介绍一个小plop脚手架,自动化创建同类型项目文件。
首先安装plop依赖,然后
image.png
创建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举例
image.png
image.png

最后 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语法)
image.png

然后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"
  }
}

然后一个最简易版就出来了。

自动化构建

把开发阶段代码转换成生产阶段代码。
image.png
image.png
类似这种的,还有取出空格,压缩文件。。。等。
常用的构建工具有
image.png

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清空

然后我们借助插件尝试对一个项目进行打包:
image.png
image.png

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包的时候把这俩文件都放上去。

首先添加image.png
bin一般是放脚手架文件我们放了zce-pages.js
然后我们查看gulp命令实际调用了什么
根据我们查看node_modules/bin/gulp.cmd文件
image.png
发现调用的是脚手架里gulp,然后我们看到
image.png
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里的命令调用了
image.png

//作为npm包发布时依赖项必须放在dependencies不然不会自动下载
我的lp-workflow-cli 已发布欢迎使用
FIS 捆绑套餐,比较典型需求尽可能集成在内部。(大而全)
该文章内容摘抄于拉钩大前端训练营

阅读 176

世界核平

10 声望
4 粉丝
0 条评论

世界核平

10 声望
4 粉丝
宣传栏