前端工程化之自動化構建

自動化構建

源代碼自動轉換爲生產環境代碼

NPM Scripts腳本實現自動化

利用npm scripts腳本實現自動化,npm scripts原理參照阮一峯老師博客

  • 鉤子機制實現構建流:
    • pre-<name>:該命令在name命令之前先啓動
    • post-<name>:該命令在name命令之後才啓動
  • npm-run-all模塊:同時並行執行多個腳本命令,如npm-p build serve同時啓動build和serve腳本命令

常用自動化構建工具

  • gulp:基於虛擬文件對象的流程,內存中操作文件,而不需要和磁盤交互。
  • grunt:基於臨時文件的流程,每一步都要將文件內容寫入磁盤,構建速度慢
  • fis:百度推出的前端構建系統,捆綁套餐(集成了很多常用構建流程)

Grunt

  • 安裝npm install grunt
  • 創建gruntfile.js,用來導出grunt要運行的任務
  • 在命令行中使用npx grunt <taskname>來啓動任務,默認任務則直接使用npx grunt啓動即可。
//gruntfile.js
module.exports = grunt => {
  // registerTask函數的第二個參數是可選的
  grunt.registerTask('foo', '任務描述', () => {
    
  })
  grunt.registerTask('bar', () => {

  })
  // default任務由串行的foo和bar任務組成
  grunt.registerTask('default', ['foo', 'bar']);
  // grunt默認是同步操作,異步操作需要先用this.async()生成一個done函數引用
  // 在異步任務結束後調用done,來告訴grunt異步任務完成了。
  grunt.registerTask('async-task', function() {
    const done = this.async();
    setTimeout(() => {
      // 具體任務代碼
      done();
    }, 1000);
  })
  // 標記失敗的任務,如果任務失敗,則後續任務不再執行
  // 通過在命令行使用--force來強制執行後續任務
  grunt.registerTask('fail', () => {
    // 在函數體中return false來標記任務失敗
    return false;
  })
  // 異步任務的失敗不能直接返回false,而是在done中傳入false
  grunt.registerTask('async-fail', function() {
    const done = this.async();
    setTimeout(() => {
      // 標記異步任務失敗,給done函數傳入false來調用
      done(false);
    }, 1000);
  })
}

Grunt的配置選項方法

module.exports = grunt => {
  // 添加配置項
  grunt.initConfig({
    // 配置選項的鍵一般與任務名一致,值爲任意類型數據 
    foo: {
      bar: '123'
    }
  })
  grunt.registerTask('foo', () => {
    grunt.config('foo.bar');
  })
}

Grunt多目標模式任務

  • 多目標模式,可以讓任務根據配置形成多個子任務
  • 在命令行中使用npx grunt build啓動多目標任務時,會顯示出兩個子任務的執行
  • 也可以直接執行子任務npx grunt build:css
module.exports = grunt => {
  // 爲多目標模式的任務指定配置,從而形成不同目標的子任務
  // 鍵與任務名一致,值爲對象,且屬性名爲子任務名
  grunt.initConfig({
    build: {
      // options不作爲子任務出現,而是作爲build任務的配置選項
      options: {
        foo: 'bar'
      }
      // 除了options之外,其他的鍵值對都會作爲子任務出現
      css: 'css',
      js: 'js',
      other: {
        // 子任務的options會遮蔽父任務的options
        options: {
          foo: 'other'
        }
      }
    }
  })
  grunt.registerMultiTask('build', function() {
    // this.options()方法獲取到build任務的options選項對象
    console.log(this.options());
    // this.target獲取子任務名,this.data獲取子任務名對應的值;
    console.log(`build task target: ${this.target}, data: ${this.data}`);
  })
}

Grunt插件的使用

  • 安裝插件,絕大多數插件的命名規範都是grunt-contrib-<name>
  • 在gruntfile.js中導入插件提供的任務
  • 根據插件文檔進行配置
  • 使用npx grunt <name>來運行插件任務
// 安裝 npm install grunt-contrib-clean
// gruntfile.js
module.exports = grunt => {
  // 爲插件進行配置,這裏配置的是多目標任務
  grunt.initConfig({
    clean: {
      temp: 'temp/app.js'
    }
  })
  // 導入插件提供的任務
  grunt.loadNpmTasks('grunt-contrib-clean');
}

常用的Grunt構建插件演示

  • 安裝load-grunt-tasks,用於減少loadNpmTasks的調用
  • 安裝grunt-sass和sassnpm install grunt-sass sass -D,編譯sass文件
  • 安裝grunt-babel和@babel/core @babel/preset-env,編譯ES6+
  • 安裝grunt-contrib-watch,啓動任務監控
const sass = require('sass');
const loadGruntTasks = require('load-grunt-tasks');

module.exports = grunt => {
  grunt.initConfig({
    sass: {
      // 爲sass任務提供選項對象,這裏指定了sass的編譯環境和sourceMap文件
      options: {
        sourceMap: true,
        implementation: sass
      }
      // 爲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只負責監控,第一次啓動watch任務不會運行其他tasks
    watch: {
      // js子任務,監控js文件,有更改則運行babel任務
      js: {
        files: ['src/js/*.js'],
        tasks: ['babel']
      },
      // css子任務,監控scss文件,有更改則執行sass任務
      css: {
        files: ['src/scss/*.scss'],
        tasks: ['sass']
      }
    }
  })
  // grunt.loadNpmTasks('grunt-sass');
  // 自動加載所有grunt插件中的任務
  loadGruntTasks(grunt);
  // 用默認任務來第一次任務
  grunt.registerTask('default', ['sass', 'babel', 'watch'])
}

Gulp

基本使用

  • 安裝gulpnpm install gulp -D,同時也會自動安裝好gulp-cli
  • 創建gulpfile.js作爲gulp的入口文件,由外界啓動的tasks掛載到exports對象上(遵循CommonJS規範),或者作爲私有任務不暴露給外部。
  • 命令行運行npx gulp <task-name>啓動任務。
任務定義與掛載
  • 每一個任務都是一個函數
  • gulp默認都是異步任務,因此,任務函數接收一個done參數,用於在任務結束時調用done()來通知gulp任務結束。
  • 任務函數如果返回流、Promise等,可以不調用done。
  • 任務函數通過掛載到exports上才能通過命令行啓動運行,沒有掛載到exports上的都是私有函數,只能由gulp在內部處理。
任務的組合
series順序執行
const { series } = require('gulp');
exports.default = series(task1, task2, task3); // 順序執行這三個任務
parallel並行執行
const { parallel } = require('gulp');
exports.default = parallel(task1, task2, task3); // 並行執行這三個任務
Gulp異步任務的三種方式

如何通知gulp異步任務的完成

回調方式
exports.callback = done => {
  console.log('回調函數方式');
  done(); // 表示異步任務完成
}
// 回調函數與node.js中的一致,都是錯誤優先方式;
exports.callback_err = done => {
  console.log('出現錯誤時的異步任務');
  // 如果任務出錯,則給done傳入的第一個參數爲異常對象
  done(new Error('task failed'))
}
Promise方式
exports.promise = () => {
  console.log('promise task');
  return Promise.resolve();
}
exports.promise_err = () => {
  console.log('promise task failed');
  return Promise.reject(new Error('task failed'));
}

// 使用async函數的方式,只要node環境支持就可以使用
const timeout = (time) => {
  return new Promise(resolve => {
    setTimeout(resolve, time);
  })
}
exports.async_task = async () => {
  await timeout(1000);
  console.log('async task');
}
返回流的方式

在任務中返回流是gulp中最常用的異步任務結束方式。在gulp中最頻繁的操作就是讀取文件流、轉換文件流、寫入文件流等待,返回一個文件流,實際上會在流的end事件觸發時,通知gulp異步任務完成了。因此,返回流、返回EmitEvent類型的對象都是可以的

const fs = require('fs');

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構建過程的核心工作原理

// gulpfile.js
// 使用node.js的原始模塊來模擬gulp的工作
const fs = require('fs');
// 導入轉換流
const { Transform } = require('stream');

export.default = () => {
  // 創建文件讀取流
  const read = fs.createReadStream('normalize.css')
  // 創建文件寫入流
  const write = fs.createWriteStream('normalize.min.css');
  // 創建轉換流,用來去除註釋,刪除空白字符
  const transform = new Transform({
    transform: (chunk, encoding, callback) => {
      // 核心轉換過程
      // chunk即讀取流中的內容(Buffer)
      const input = chunk.toString(); // 將字節數組轉爲字符串
      const output = input.replace(/\s+/g, '') // 刪除空白字符
        .replace(/\/\*.+?\*\//g, '') // 刪除註釋
      callback(null, output); // 回調函數
    }
  })
  // 把讀取流傳入轉換流,再傳入寫入流
  read
    .pipe(transform)
    .pipe(write);
  // 返回讀取流
  return read;
}

Gulp文件操作API和插件的使用

構建流程:Gulp讀取流 + 插件的轉換流 + Gulp的寫入流
Gulp採用同名文件覆蓋的策略。

# 安裝gulp-clean-css插件,壓縮CSS代碼
npm install gulp-clean-css --dev
# 安裝gulp-rename插件
npm install gulp-rename --dev
const { src, dest } = require('gulp');
const cleanCss = require('gulp-clean-css');
const rename = require('gulp-rename');

exports.default = () => {
  return src('src/normalize.css')
    .pipe(cleanCss())
    .pipe(rename({ extname: '.min.css' }))
    .pipe(dest('dist'));
}

Gulp案例

樣式編譯
# 安裝gulp-sass用來編譯scss文件,同時會安裝node-sass
# node-sass是一個C++模塊,有時候國外的源下載很慢,需要爲這個包配置單獨的鏡像源
npm install gulp-sass --dev
  • src(globs, [options])
    • options.base:默認的base爲glob base,即特殊字符之前的路徑。在dest()寫入流時,會將base刪去。因此源文件的目錄結構在編譯後就會丟失。如果手動指定base,則可以保留源文件的目錄結構。
// gulpfile.js
const { src, dest } = require('gulp');
const sass = require('gulp-sass');
// 建立style私有任務
const style = () => {
  // { base: 'src' }指定一個基準路徑,這樣在生成文件到dist路徑下時
  // src後面的路徑會原封不動複製到dist路徑下,這裏涉及到了glob base概念
  // 具體可以查閱gulp的官方文檔
  return src('src/assets/styles/*.scss', { base: 'src' })
    /* sass()默認不會轉換_開頭的.scss文件
    因爲_開頭的sass文件是作爲其他.scss的依賴文件存在的
    會直接被其他.scss文件引入,然後再一起編譯
    outputStyle配置屬性來指定輸出文件的格式 */
    .pipe(sass({ outputStyle: 'expanded' }))
    .pipe(dest('dist'))
}
module.exports = { style }
腳本編譯
# 安裝gulp-babel編譯ES6+,這裏還需要安裝@babel/core、@babel/preset-env
npm install gulp-babel @babel/core @babel/preset-env --dev
// gulplfile.js
const babel = require('gulp-babel');

const script = () => {
  return src('src/assets/scripts/*.js', { base: 'src' })
    .pipe(babel({
       presets: [
         '@babel/preset-env'
       ]
     }))
    .pipe(dest('dist'))
}
module.exports = { style, script }
模板文件(HTML文件)的編譯

這裏使用了swig模板引擎

# 安裝gulp-swig 
npm install gulp-swig --dev
// gulpfile.js
const { series, parallel } = require('gulp');
const swig = require('gulp-swig');
// data作爲模板引擎的渲染數據上下文對象,在後面的封裝工作流中data會被移除
const data = {
  //這裏是具體的數據
};

const page = () => {
  // 這裏的base設置其實沒有必要,但爲了統一
  return src('src/*.html', { base: 'src' })
    .pipe(swig({ 
      data: data,
      // cache:false保證swig引擎的緩存機制不生效,這樣頁面的修改就可以在開發服務器上實時反映出來
      cache: false 
     }))
    .pipe(dest('dist'))
}
// module.exports = { style, script, page }
/* 創建一個並行的組合任務 */
const compile = parallel(style, script, page)
module.exports = { compile }
圖片和字體文件的轉換

圖片進行壓縮,對圖片中的元信息進行刪除

# 安裝gulp-imagemin壓縮圖片,無損壓縮,svg圖片做了代碼壓縮格式化
npm install gulp-imagemin --dev
// gulpfile.js
const { series, parallel } = require('gulp');
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/fonts/**', { base: 'src' })
    .pipe(imageMin())
    .pipe(dest('dist'))
}
const compile = parallel(style, script, page, image, font)
module.exports = { compile }
其他文件以及文件清除
# 安裝del,用來清除文件
npm install del --dev
// gulpfile.js
const { series, parallel } = require('gulp');
const del = require('del');

// 額外任務,就是複製public目錄下的所有文件到dist目錄下
const extra = () => {
  return src('public/**', { base: 'public' })
    .pipe(dest('dist'))
}

const clean = () => {
  return del(['dist'])
}
// 先清除,然後再構建
const build = series(clean, parallel(compile, extra));

module.exports = { compile, build }
自動載入插件
# 安裝gulp-load-plugins,提供自動加載插件的功能,這樣避免gulpfile.js中導入插件的語句太多
npm install gulp-load-plugins --dev
// gulpfile.js
const loadPlugins = require('gulp-load-plugins');
// 使用loadPlugins方法將所有插件導入,返回一個plugins命名空間
// 所有插件都成爲plugins的屬性
// 屬性名爲去掉gulp-前綴,如果插件名爲gulp-xxx-yy,則會將xxx-yy轉爲駝峯命名方式xxxYy
// 代碼裏的插件都需要使用plugins.xxx來引用
const plugins = loadPlugins();

開發服務器與熱更新
# 安裝browser-sync
npm install browser-sync --dev
// gulpfile.js
const { series, parallel, watch } = require('gulp');
const browserSync = require('browser-sync');
// 創建一個開發服務器
const bs = browserSync.create();

const serve = () => {
  // gulp.watch來監控以下路徑下的文件,如果文件更新則啓動對應的任務
  watch('src/assets/styles/*.scss', style);
  watch('src/assets/scripts/*.js', script);
  watch('src/*.html', page);
  /* 圖片、字體和public目錄下的文件反覆去編譯是沒有意義的,只需要在上線前編譯一次即可
  因此,沒必要在開發階段每次都編譯到dist目錄下
  在開發階段,只要保留在原來的src和public目錄下即可,讓服務器去serve這些資源本來的目錄*/
  /* 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: 8080,
    // 取消自動打開瀏覽器
    open: false,
    // 標識browser-sync監控的文件,如果有更改則自動熱更新瀏覽器
    files: 'dist/**',
    // 配置服務器的信息
    server: {
      /* 配置網站根目錄,這裏是dist目錄,src和public目錄用來在開發階段
      serve那些只在上線前編譯一次的靜態資源,如圖片字體等 */
      baseDir: ['dist', 'src', 'public'],
      // 配置網站中的路由,key是要匹配的url,value是以當前工作目錄的相對路徑
      // 這裏將url: /node_modules的路徑映射到node_modules
      // 從而可以訪問到項目node_modules路徑下的資源
      // 這裏之所以這樣配置,是因爲我們引用了項目node_modules中的資源,但沒有把該資源複製到dist目錄下
      routes: {
        '/node_modules': 'node_modules'
      }
    }
  })
}
// 編譯html、CSS和JS文件
const compile = parallel(style, script, page)
// 上線之前的構建任務build
const build = series(clean, parallel(compile, image, font, extra));
// 開發階段的編譯任務,主要就是編譯完HTML、CSS和JS後,開啓服務器
const develop = series(compile, serve);
module.exports = { compile, build, serve }
useref文件引用處理

在這裏,我們將上文中在HTML文件中引用的node_modules中的bootstrap.css文件處理一下。

useref的使用方式

  • 將HTML文件中的CSS和JS文件引用進行合併(但不壓縮),文件流傳遞下去
  • 解析HTML文件中的build block語法規則來進行文件合併,解析之後在新生成的HTML文件中將build block規則註釋去掉
  • 生成引用了合併文件的新的HTML文件,以及合併後的文件。
  • build blocks規則如下所示:
<!-- build:<type>(alternate search path) <path> <parameters> -->
... HTML Markup, list of script / link tags.
<!-- endbuild -->
  • <type>:css | js | remove,remove只會移除註釋,並不產生新文件
  • alternate search path:默認情況下,輸入文件的路徑是相對於當前解析的文件。alternate search path提供一個更改該路徑的值。
  • path:表示輸出的文件路徑,也就是合成後的文件路徑
  • parameters:表示額外的參數

例如:
處理前的HTML:

  • css/one.css和css/two.css合併爲css/combined.css
  • scripts/one.js和scripts/two.js合併爲scripts/combined.js
<html>
<head>
    <!-- build:css css/combined.css -->
    <link href="css/one.css" rel="stylesheet">
    <link href="css/two.css" rel="stylesheet">
    <!-- endbuild -->
</head>
<body>
    <!-- build:js scripts/combined.js -->
    <script type="text/javascript" src="scripts/one.js"></script> 
    <script type="text/javascript" src="scripts/two.js"></script> 
    <!-- endbuild -->
</body>
</html>

處理後的HTML,引用了合併後的文件

<html>
<head>
    <link rel="stylesheet" href="css/combined.css"/>
</head>
<body>
    <script src="scripts/combined.js"></script> 
</body>
</html>
# 安裝gulp-useref
npm install gulp-useref --dev
// gulpfile.js
const useref = () => {
  return src('dist/*.html', { base: 'dist' })
    .pipe(plugins.useref({
      /* searchPath用來標識那些在HTML文件中引用的資源路徑
       或者說在項目中去哪裏找這些引用的資源
       相對於當前工作目錄 */
      searchPath: ['dist', '.'] 
    }))
    .pipe(dest('dist'))
}
module.exports = { clean, compile, build, develop, useref }
文件壓縮(HTML、CSS和JS)
  • 延續自上文的useref插件,從useref插件返回的流中開始壓縮工作。
  • 由於三種文件採用不同的壓縮插件,因此需要使用gulp-if來進行文件類型判斷。
# 安裝壓縮包
npm install gulp-htmlmin gulp-uglify gulp-clean-css --dev
# 安裝gulp-if來判斷
npm install gulp-if --dev
// gulpfile.js
/* 注意,在運行useref任務之前,可能需要先運行編譯compile任務
因爲useref任務取決於HTML文件中的build blocks語法註釋
如果之前已經運行過useref,那麼HTML文件中就沒有了build blocks註釋 */
const useref = () => {
  return src('dist/*.html', { base: 'dist' })
    .pipe(plugins.useref({
      /* searchPath用來標識那些在HTML文件中引用的資源路徑
       或者說在項目中去哪裏找這些引用的資源
       相對於當前工作目錄 */
      searchPath: ['dist', '.'] 
    }))
    .pipe(plugins.if(/\.js$/, plugins.uglify()))
    .pipe(plugins.if(/\.css$/, plugins.cleanCss()))
    .pipe(plugins.if(/\.html$/, plugins.htmlmin({
     collapseWhitespace: true,
     // 將HTML文件中<style>和<script>中的代碼也壓縮
     minifyCSS: true,
     minifyJS: true
     })))
    // 這裏把dist換成了release,防止出現在同一個文件夾下讀取和寫入造成的衝突
    .pipe(dest('release'))
}
module.exports = { clean, compile, build, develop, useref }
重新規劃構建過程

前面由於useref的使用,爲了防止寫入與讀取的衝突,導致更換到release目錄作爲發佈目錄,但release目錄中沒有圖片字體等靜態資源,這些靜態資源是構建在dist目錄下的,因此需要對構建過程重新規劃一下。
整個構建過程:編譯 -> 臨時目錄temp: useref + 圖片等靜態資源編譯 -> dist目錄

// gulpfile.js
/* clean需要改爲del(['dist', 'temp'])
style、script、page這幾個任務需要修改爲dest('temp')
serve任務的baseDir也要改爲'temp'*/
// 將build任務修改
const build = series(
  clean,
  parallel(
    series(compile, useref),
    image,
    font,
    extra
  )
)
任務導出與私有任務
  • 在完成整個構建流程後,對哪些任務需要導出給外部使用(接口),哪些任務作爲內部私有任務進行規劃。
  • 我們這裏選擇了clean、develop、build三個任務導出給外部作爲接口使用,其他私有任務都封裝在這三個任務中。
  • 在package.json中使用npm scripts來運行gulp任務。

封裝工作流:在多個項目中複用自動化構建過程

封裝工作流:將完成的一套構建流程在多個項目中複用(不是簡單複製粘貼)
將已經構建好的gulpfile.js、gulp和依賴項作爲一個新的NPM模塊(比如我們命名爲gulp-build)發佈,在新的項目中安裝gulp-build模塊即可使用封裝好的工作流。

封裝流程
  • 將gulpfile.js的內容添加到gulp-build模塊的入口文件index.js中,通常目錄結構爲lib/index.js。
  • 開發依賴項拷貝到gulp-build模塊的依賴項中,這樣當我們安裝gulp-build模塊時,就會自動安裝那些依賴模塊。
  • 入口文件中還應該提供一個配置項,用於針對不同項目的不同情況(我們在進行模板編譯的時候需要這個配置項來作爲模板數據的上下文對象)。每個項目都可以提供一個配置文件,gulp-build模塊讀取該配置文件,與自身的默認配置項做一個合併,作爲整體的配置項。
    • 抽象路徑的配置:在原來的gulpfile.js中,對於文件的讀取和寫入的路徑都是固定的,無法針對不同項目的目錄結構靈活配置。因此,我們將這些路徑做一個抽象配置,在默認配置對象中定義默認的路徑,通過讀取項目中的配置文件來替換這些路徑。
  • 包裝gulp-cli,構建模塊自己的CLI。
    • 因爲我們的gulp-build模塊內部已經有gulpfile.js文件(就是模塊的入口文件index.js)了,沒有必要在使用環境中再次提供一個gulpfile.js來導入gulp-build中的index.js文件,因此,我們只需要在使用環境下,讓gulp運行gulp-build中的index.js即可,這就需要提供一個命令行接口在使用環境下直接運行index.js。
    • gulp-cli提供了在命令行中傳入參數的方式來指定gulpfile.js的路徑gulp <task-name> --gulpfile <gulpfile-path>。同時,可能還需要對工作目錄做一下指定(因爲gulp默認將gulpfile.js所在路徑作爲工作目錄)gulp <task-name> --gulpfile <gulpfile-path> --cwd <cwd-path>。但是在命令行中傳參數會顯得很麻煩,所有我們給gulp-build模塊提供一個自定義CLI用以代替,將命令行傳參轉換爲CLI腳本文件的執行。CLI的入口腳本文件gulp-build.js一般放在包的bin目錄。
      • package.json中bin字段用以指定CLI的方式執行的入口文件,main字段表示用require()加載模塊時的入口文件。
// bin/gulp-build.js,CLI腳本文件
#!/usr/bin/env node
// process.argv是一個數組,其中數組元素包含了從命令行傳遞來的參數
process.argv.push('--cwd'); // 命令行參數名
process.argv.push(process.cwd()); // 參數值
process.argv.push('--gulpfile'); // 參數名
// 參數值,這裏使用require.resolve()返回模塊入口文件的絕對路徑。
// ..是相對路徑,向上找到模塊根路徑,然後會自動查看package.json中的main字段
process.argv.push(require.resolve('..')); 
// 啓動gulp,實際上都是require('gulp-cli')()
// gulp-cli的入口文件導出了一個run的方法,這裏都是執行run方法
require('gulp/bin/gulp')
// gulp-build模塊的入口文件index.js,
// 這裏只寫出對上文gulpfle.js的補充代碼
const cwd = process.cwd(); // 獲取執行命令的當前工作目錄
let config = { 
  // 這裏是默認配置的代碼
  // 對路徑的默認配置選項
  build: {
    
  }
}; // 聲明一個配置項

try {
  const loadConfig = require(path.join(cwd, 'page.config.js'));
  config = Object.assign({}, config, loadConfig);
} catch(e) {
  //
}
/* 如果在測試環境中運行構建時出現錯誤信息,說找不到某個模塊,
是因爲配置時,只聲明瞭該模塊的名稱,
則會去測試環境的node_modules目錄下找,肯定找不到。
因爲該模塊並不是在測試環境的node_modules目錄下,
而是在gulp-build模塊的node_modules目錄下。
因此,需要在配置時,
將聲明模塊的語句改爲require(<package-name>)的形式,
這樣在工作時,
require()語句會去gulp-build模塊的node_modules目錄下找<package-name>模塊,
因爲我們的index.js是在gulp-build模塊裏。*/
// 這裏以@babel/preset-env演示一下
// 原來的代碼,聲明模塊名稱,這樣babel就會在當前工作目錄的node_modules下找模塊
.pipe(plugins.babel({ presets: ['@babel/preset-env'] })); 
/* 現在更改後的代碼,用require()來加載模塊,
這樣就直接加載了該模塊給babel用,不需要babel再自己加載了 */
.pipe(plugins.babel({ presets: [ require('@babel/preset-env') ] })); 
測試該構建模塊
  • 測試gulp-build模塊,使用npm link到全局,然後在測試環境的項目下npm link gulp-build。
  • 沒有包裝gulp-cli時
    • 測試項目下,在gulpfile.js中導入gulp-build包,因爲gulp-build包的入口文件就是原來的gulpfile.js,所以gulp-build包默認導出了一些任務(這裏導出了clean、develop、build三個任務)。
    • 安裝gulp-cli,從而可以在命令行啓動gulp
    • 安裝gulp,從而可以啓動任務
  • 包裝gulp-cli,有了自己的CLI時,就可以直接使用了
// 測試項目中的package.json
"scripts": {
  "clean": "gulp clean",
  "develop": "gulp develop",
  "build": "gulp build"
}
// 測試項目中的gulpfile.js
module.exports = require('gulp-build');
// 如果想要在終端看到任務名字與對應的執行情況,需要單獨導出任務名稱,
// 這樣gulp才能從gulpfile.js中得到這些任務名稱。
// 而不是像上面的直接導出一個require的結果。

FIS

  • 高度集成常見的構建流程
  • 內置webServer

基本使用

# 安裝
npm install fis3 -g --dev
  • 資源定位:將開發文件中的相對路徑,在構建後,生產文件中變爲絕對路徑。
  • 使用配置文件進行聲明式構建流程
// fis的配置文件
fis.match('*.{js, scss, png}', {
  release: 'assets/$0'
})

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')
})

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章