美文网首页
使用Glup完成项目自动化构建

使用Glup完成项目自动化构建

作者: 翔子丶 | 来源:发表于2021-01-17 18:20 被阅读0次
    简介

    自动化构建是前端工程化的一个重要组成部分,将源代码转换为生成代码;这样就可以在开发过程中使用提高效率的语法、规范和标准

    比如我们可以通过:

    • 使用ECMAScript提高代码效率和质量
    • 使用Sass增强css的可编程性
    • 利用模板引擎抽象页面中重复的页面

    这些在浏览器中是不支持的,所有可以通过自动化构建工具转换这些不被支持的特性

    • 示例:通过Sass增强css的可编程性

      $body-bg: #000;
      $body-color: #f00;
      body {
        margin: 0 auto;
        padding: 20px;
        max-width: 800px;
        background-color: $body-bg;
        color: $body-color;
      }
      
      1. 首先安装sass模块,作为开发依赖yarn add sass --dev

      2. 安装完成可以直接使用node_modules下的sass命令npx sass

      3. 将sacc文件转换为cssnpx sass scss/main.scss css/style.css,并且自动添加source-map文件

        body {
          margin: 0 auto;
          padding: 20px;
          max-width: 800px;
          background-color: #000;
          color: #f00;
        }
        
      4. 可以将命令定义在NPM Scripts中,便于使用,在package.json中添加命令

      5. NPM Scripts是实现自动化构建工作流最简单的方式,安装browser-sync模块启动测试服务器运行项目 yarn add browser-sync --dev

        "scripts": {
            "build": "sass scss/main.scss css/style.css",
            "serve": "browser-sync ."
        }
        
      6. yarn serve就可以直接运行我们的项目

      7. 我们可以给sass添加--watch参数用来监听文件变化,一旦scss文件发生改变就自动转换为css文件

      8. 使用npm-run-all模块run-p命令实现多个命令依次执行 yarn add npm-run-all --dev,同时给browser-sync模块添加--files属性监听项目文件变化,更新浏览器界面,避免手动刷新浏览器

        "scripts": {
            "build": "sass scss/main.scss css/style.css --watch",
            "serve": "browser-sync . --files \"css/*.css\"",
            "start": "run-p build serve"
        }
        
    常用自动化构建工具
    • Grunt
    • Gulp
    • FIS
    Grunt使用
    1. 安装grunt模块yarn add grunt

    2. 添加gruntfile.js文件,作为grunt的入口文件

    3. 使用grunt.registerTask注册任务,通过yarn grunt xxx任务名的方式运行任务

      // gruntfile.js 入口文件 定义一些需要 Grunt 自动执行的任务 
      // grunt 对象中提供一些创建任务时会用到的 API
      module.exports = (grunt) => {
        grunt.registerTask('foo', 'a sample task', () => {
          console.log('hello grunt')
        })
      
        grunt.registerTask('bar', () => {
          console.log('other task')
        })
        // default 是默认任务名称 第二个参数可以指定此任务的映射任务 通过 grunt 执行时可以省略
        // 这里映射的任务会按顺序依次执行,不会同步执行
        grunt.registerTask('default', ['foo', 'bar'])
        // 也可以在任务函数中执行其他任务  
        grunt.registerTask('run-task', () => {
          // foo 和 bar 会在当前任务执行完成过后自动依次执行
          grunt.task.run('foo', 'bar')
          console.log('run-task')
        })
        // 由于函数体中需要使用 this,所以这里不能使用箭头函数
        grunt.registerTask('async-task', function () {
          // 异步方式需要使用this.async() 方法创建回调函数
          const done = this.async()
          setTimeout(() => {
            console.log('async task working~')
            done()
          }, 1000)
        })
      }
      
    image-20210116155205854.png
    1. 标记任务失败

      module.exports = (grunt) => {
        // 返回false 则意味任务执行失败
        grunt.registerTask('bad', () => {
          console.log('bad working')
          return false
        })
        grunt.registerTask('foo', 'a sample task', () => {
          console.log('hello grunt')
        })
      
        grunt.registerTask('bar', () => {
          console.log('other task')
        })
      
        // 如果任务执行失败 则后续不再进行 此时添加--force参数强制执行
        grunt.registerTask('default', ['foo', 'bad', 'bar'])
      
        // 异步函数中标记当前任务执行失败的方式是为回调函数指定一个 false 的实参
        grunt.registerTask('bad-async', function () {
          const done = this.async()
          setTimeout(() => {
            console.log('async task working~')
            done(false)
          }, 1000)
        })
      }
      
    image-20210116155800989.png
    1. Grunt配置方法

      module.exports = (grunt) => {
        // grunt.initConfig() 用于为任务添加一些配置选项
        grunt.initConfig({
          // 键一般对应任务的名称
          // 值可以是任意类型的数据
          foo: {
            bar: 'bar',
          },
        })
      
        grunt.registerTask('foo', () => {
          // 通过grunt.config获取
          console.log(grunt.config('foo.bar')) // bar
        })
      }
      
    2. Grunt多目标任务

      module.exports = (grunt) => {
          // 多目标模式,可以让任务根据配置形成多个子任务
          grunt.initConfig({
              build: {
                  js: 1,
                  css: 2,
              },
          })
          grunt.registerMultiTask('build', function () {
              console.log(`task: build, target: ${this.target}, data: ${this.data}`)
          })
      }
      grunt.initConfig({
          build: {
              options: {
                  msg: 'task options',
              },
              foo: {
                  options: {
                      msg: 'foo target options',
                  },
              },
              bar: '456',
          },
      })
      
      grunt.registerMultiTask('build', function () {
          // options可以获取对应的options 如果子任务有options会被覆盖
          console.log(this.options())
      })
      

      可以单独运行build:css,也可以一次运行

    image-20210116160716843.png
    1. Grunt常用插件

      const sass = require('sass')
      const loadGruntTasks = require('load-grunt-tasks')
      
      module.exports = grunt => {
        grunt.initConfig({
          sass: {
            options: {
              sourceMap: true,
              implementation: sass
            },
            main: {
              files: {
                'dist/css/main.css': 'src/scss/main.scss'
              }
            }
          },
          babel: {
            options: {
              sourceMap: true,
              presets: ['@babel/preset-env']
            },
            main: {
              files: {
                'dist/js/app.js': 'src/js/app.js'
              }
            }
          },
          watch: {
            js: {
              files: ['src/js/*.js'],
              tasks: ['babel']
            },
            css: {
              files: ['src/scss/*.scss'],
              tasks: ['sass']
            }
          }
        })
      
        // grunt.loadNpmTasks('grunt-sass')
        loadGruntTasks(grunt) // 自动加载所有的 grunt 插件中的任务
      
        grunt.registerTask('default', ['sass', 'babel', 'watch'])
      }
      
    Gulp
    1. Gulp基本使用

      • yarn init --yes初始化项目
      • 安装gulp模块yarn add gulp --dev
      • 创建gulpfile.js文件,gulp的入口文件
      // gulp 的任务函数都是异步的
      // 可以通过调用回调函数标识任务完成
      exports.foo = done => {
        console.log('foo task working~')
        done() // 标识任务执行完成
      }
      
      // default 是默认任务
      // 在运行是可以省略任务名参数
      exports.default = done => {
        console.log('default task working~')
        done()
      }
      
    image-20210116162143650.png
    1. Gulp组合任务

      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)
      // 让多个任务同时执行 并行任务 编译js、css任务
      exports.bar = parallel(task1, task2, task3)
      
    image-20210116162659123.png
    1. Gulp异步任务

      const fs = require('fs')
      
      exports.callback = done => {
        console.log('callback task')
        done()
      }
      // 错误优先 一旦出错 就不会在执行后面的任务
      exports.callback_error = done => {
        console.log('callback task')
        done(new Error('task failed'))
      }
      
      exports.promise = () => {
        console.log('promise task')
        return Promise.resolve()
      }
      // 失败状态 Gulp认为是失败任务 同样种植后面任务
      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 read = fs.createReadStream('yarn.lock')
        const write = fs.createWriteStream('a.txt')
        read.pipe(write)
        return read
      }
      
      exports.stream1 = done => {
        const read = fs.createReadStream('yarn.lock')
        const write = fs.createWriteStream('a.txt')
        read.pipe(write)
        read.on('end', () => {
          done()
        })
      }
      
    2. Gulp构建过程核心工作原理

      const fs = require('fs')
      const { Transform } = require('stream')
      
      // 过程 输入 -- 加工 -- 输出
      exports.default = () => {
        // 文件读取流
        const readStream = fs.createReadStream('normalize.css')
        // 文件写入流
        // const writeStream = fs.createWriteStream('normalize.bak.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, '')
            // 第一个参数为错误对象 如果没有 返回null
            callback(null, output)
          },
        })
      
        // 把读取出的文件流导入写入文件流
        return readStream
          .pipe(transformStream) // 转换
          .pipe(writeStream) // 写入
      }
      

      Gulp的核心工作原理就是输入(读取流) -- 加工(转换流) -- 输出(写入流)

    3. Gulp文件操作API

      // src方法创建读取流 借助插件提供的转换流实现文件加工 dest方法创建写入流
      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'))
      }
      
    4. Gulp实现自动化工作流

      /**
       * 1.首先yarn add gulp --dev安装gulp
       * 2.使用src和dest输入流和输出流 此时yarn gulp style 并引入
       * 3.使用gulp-sass转换流 转换sass文件 此时 dist文件夹中存在以前未被删除的scss文件 需手动删除
       * 4.转换ES6语法 使用插件gulp-babel yarn add gulp-babel @babel/core @babel/preset-env --dev 并引入
       * 5.html文件编译 使用gulp-swing yarn add glup-swig --dev 需要定义data完成对EJS语法参数的填充
       * 6.因为三个构建动作可并行执行 所以使用parallel提高构建效率
       * 7.图片和字体文件转换 yarn add gulp-imagemin --dev 并引入
       * 8.至此 src下的文件已经处理完成 需要拷贝public下的文件
       * 9.开发体验增强 自动清除dist文件 yarn add del --save 需要先清除再编译 需要串行任务series
       * 10.自动加载插件 yarn add glup-load-plugins --dev 会自动加载所有插件 所以不用再逐个导入 plugins.xxx使用
       * 11.热更新开发服务器 yarn add browser-sync --dev 启动web服务器
       * 12.监视文件变化 自动更新浏览器 watch监听文件路径匹配符 并执行对应的任务
       * 13.构建过程优化 开发阶段执行的任务 和 不许执行的任务单独分开dev
       * 14.此时基本构建任务已经完成 但html文件中像node_module/xxx/xx.css 这样的css或js文件并没有拷贝到dist目录 使用useref yarn add gulp-useref --dev 将样式文件和js文件合并到新的文件中供页面使用
       * 15.将得到的文件进行压缩处理 yarn add gulp-htmlmin gulp-uglify gulp-clean-css --dev 对不同的文件进行不同处理 需要 gulp-if插件
       * 16.保持项目风格 所以需要一个临时文件存储未被useref的文件temp
       * 17.将需要的任务(clean,build,develop等)放在Npm Scripts中去 并且将temp dist目录添加到git忽略文件中
       */
      const { src, dest, parallel, series, watch } = require("gulp");
      const del = require("del");
      const browserSync = require("browser-sync");
      
      const loadPlugins = require("gulp-load-plugins");
      const { init } = require("browser-sync");
      // 会自动加载所有插件 所以不用再逐个导入
      const plugins = loadPlugins();
      // 自动创建开发服务器
      const bs = browserSync.create();
      // html模板文件参数
      const 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(),
      };
      
      const clean = () => {
        return del(["dist", "temp"]);
      };
      const style = () => {
        // 需要按照项目的目录输出文件 { base: 'src' }
        return (
          src("src/assets/styles/*.scss", { base: "src" })
            .pipe(plugins.sass({ outputStyle: "expanded" }))
            // 需放入临时目录
            .pipe(dest("temp"))
            // 以流的方式推到浏览器 就不需要监视files: 'dist/**'
            .pipe(bs.reload({ stream: true }))
        );
      };
      
      // js编译
      const script = () => {
        return src("src/assets/scripts/*.js", { base: "src" })
          .pipe(plugins.babel({ presets: ["@babel/preset-env"] }))
          .pipe(dest("temp"))
          .pipe(bs.reload({ stream: true }));
      };
      
      // html
      const page = () => {
        return src("src/*.html", { base: "src" })
          .pipe(plugins.swig({ data }))
          .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,
          port: 2080,
          // open: false,
          // 目标文件下的内容发生变化时 自动更新浏览器
          // files: 'dist/**',
          server: {
            // 先从数组第一个去找 找不到的话依次找下去
            baseDir: ["temp", "src", "public"],
            // 先看routes下的配置
            routes: {
              "/node_modules": "node_modules",
            },
          },
        });
      };
      
      const useref = () => {
        return (
          src("temp/*.html", { base: "temp" })
            .pipe(plugins.useref({ searchPath: ["temp", "."] }))
            // 对新创建的文件进行压缩
            .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);
      // 上线之前执行的任务
      const build = series(
        clean,
        parallel(series(compile, useref), image, font, extra)
      );
      const develop = series(compile, serve);
      module.exports = {
        clean,
        build,
        develop,
      };
      
    5. 封装工作流

      1. 如何提取项目当中共同的自动化构建任务(封装自动化构建工作流)在别的项目中直接使用

      2. 首先全局引入配置模块,yarn link,然后在需要使用的地方引入yarn link zce-pages

      3. gulpfile.js中直接导出module.exports = require('zce-pages')

      4. 创建pages.config.js将不应该在公共模块中出现的文件抽象出来,属于项目使用,不应该添加到公共模块

      5. 在模板中获取使用模块的路径const cwd = proces.cwd()

      6. 创建config默认配置文件,将从pages.config.js文件获取的配置与默认配置整合

        let config = {}
        try {
          const loadConfig = require(path.join(cwd, '/pages.config.js'))
            config = Object.assign({}, config, loadConfig)
        } catch (e) {}
        
      7. 需要用到配置的地方,使用config.xx代替

        const page = () => {
          return src("src/*.html", { base: "src" })
            .pipe(plugins.swig({ data: config.data }))
            .pipe(dest("temp"))
            .pipe(bs.reload({ stream: true }));
        };
        
      8. 抽象路径配置

        // 将项目中有用到的目录的地方进行替换
        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/**",
            },
          },
        };
        
      9. 包装Gulp CLI

        包装的目的:可以在使用的项目中不需要gulpfile的同时还能使用

        • 首先删除gulpfile文件,再次运行gulp命令,会提示没有gulpfile文件

        • 可以通过指定gulpfile文件目录设置和当前文件目录使用,yarn gulp clean --gulpfile .\node_modules\zce-pages\lib\index.js --cwd .,不过很麻烦

        • 可以在zce-pages中提供cli,将gulp-cli包装到zce-pages中

        • 首先在zce-pages项目package.json中添加"bin": "bin/zce-pages.js"

        • 创建zce-pages.js文件,对gulp-cli的调用放到这个文件中,需要添加文件头#!/usr/bin/env node

        • node_modeuls/bin/gulp.cmd是gulp的执行文件,主要是这句代码node "%~dp0\..\gulp\bin\gulp.js" %*,找到gulp.js文件,执行require('gulp-cli')()而已,所以在我们的zce-pages.js文件中直接调用gulp.js文件require('gulp/bin/gulp')即可

        • 此时,再次gulp命令即可成功,只是还没有gulpfile文件No gulpfile found,此时需要指定gulpfile文件目录

        • 使用process.argv指定--cwd和--gulpfile文件目录

          process.argv.push("--cwd");
          process.argv.push(process.cwd());
          process.argv.push("--gulpfile");
          // 自动找package.json中配置的main目录
          process.argv.push(require.resolve(".."));
          require("gulp/bin/gulp");
          
        • 此时,将gulp包装到zce-pages模块,其他项目中不安装gulp同时也可以使用gulp-cli命令

    相关文章

      网友评论

          本文标题:使用Glup完成项目自动化构建

          本文链接:https://www.haomeiwen.com/subject/omqzaktx.html