gulp 的使用(八):分离环境

这是我参与更文挑战的第21天,活动详情查看: 更文挑战

前言

在上一篇文章 gulp 的使用(七):使用 babel 转译 js 里,介绍了如何使用 babel 转译 js。

我们都知道有时候程序是分多个环境,如:开发环境和发布环境。在这一篇文章里,将介绍如何在 gulp 项目里去分离环境。

环境变量

通常前端 node 项目的环境的区分都是依赖于 process.env.NODE_ENV 这个变量,那么我们需要在程序启动或打包时去改变这个变量的值,让程序根据这个变量去做一些改变。

在我写的 webpack5 系列文章 webpack5 的使用(二):多个环境配置 里的 “环境变量 NODE_ENV” 小节里也有介绍到如何使用 NODE_ENV,在这篇文章仅简单做介绍 NODE_ENV。

在 windows 命令行里,我们可以需要使用 set 命令来设置环境变量;而在 linux 命令行里需要使用 export 命令来设置环境变量。这里建议使用 cross-env 插件来统一设置环境变量的命令,不需要再考虑是什么系统了。

安装 cross-env

npm i -D cross-env
复制代码

修改项目里的 package.json 的 scripts

{
    ...
    "scripts": {
        ...
        "build": "cross-env NODE_ENV=production gulp build",
        "dev": "cross-env NODE_ENV=development gulp"
    },
    ...
}
复制代码

我们在 gulpfile.js 写上测试代码。

console.log('环境变量:' + process.env.NODE_ENV)
复制代码

运行 npm run build,可以看到终端输出了下面内容。

image.png

build 命令的 NODE_ENV=production 起作用了。

分离环境

分离 gulpfile.js 配置

事实上,gulpfile.js 通常并不需要环境分离,不像 webpack 的配置,毕竟它是以任务为单位,并且任务可以随意组合,像下面用两个 exports 即可分离开发环境和生产环境。

// 运行 gulp 命令,就是开发环境
exports.default = series(clean, html, libJs, js, css, scss, libCss, img, devServer, watcher)
// 运行 build 命令,就是生产环境
exports.build = series(clean, html, libJs, js, css, scss, libCss, img)
复制代码

但是,当 gulpfile.js 配置过多的时候怎么办呢?我们应该将每个小任务或者一个系列任务(如:专门处理 js 的任务)拆分放到一个个文件里,在 gulpfile.js 引入这些文件来调用里面的任务。

下面以 js 系列任务为例。

我们先在根目录创建一个 gulpfile-js.js 文件,用于存放 js 相关任务。

const { src, dest } = require('gulp')
const webpack = require('webpack-stream')
const webpackConfig = require("./webpack.config.js")
const named = require('vinyl-named')
const path = require('path')
const plumber = require('gulp-plumber')
const changed = require('gulp-changed')
const uglify = require('gulp-uglify')

function js() {
  return src(['src/js/**/*.js'])
    .pipe(changed('dist/js/**/'))
    // .pipe(babel({
    //   presets: ['@babel/env'],
    //   plugins: ['@babel/transform-runtime']
    // }))
    .pipe(named(function (file) {
      return file.relative.slice(0, -path.extname(file.path).length)
    }))
    .pipe(webpack(webpackConfig))
    .pipe(plumber())
    .pipe(uglify())
    .pipe(dest('dist/js'))
}

function libJs() {
  return src(['src/lib/**/*.js'])
    .pipe(changed('dist/lib/**/'))
    .pipe(plumber())
    .pipe(dest('dist/lib'))
}

module.exports = {
  js, libJs
}
复制代码

然后,修改 gulpfile.js,将 js 相关任务注释掉,并且引入刚才的 gulpfile-js.js 文件

const { js, libJs } = require('./gulpfile-js')
复制代码

运行 npm run build,可以发现 js 任务执行成功。

当然,这种做法有自身的缺陷,它以任务为单位拆分为不同文件,而不是以环境为单位创建不同的任务,这可能会导致在同一个任务里,难以根据不同环境区别对待,这个时候可以用 process.env.NODE_ENV 去做一些判断,或者可以创建一个新的任务。

如果你不喜欢这种“组装任务”区分环境的做法,而是要有一个很明确的环境分离边界,你也可以在根目录里创建两个很明确区分环境的文件:gulpfile.dev.js 和 gulpfile.prod.js,然后像上面那样在 gulpfile.js 做环境判断分别导出导入相关文件,在这里,就不做演示了。

分离 webpack-stream 的 webpack 配置

我在文章 webpack5 的使用(二):多个环境配置 里已经很详细介绍过 webpack 的配置分离了,有需要的小伙伴可以查阅这篇文章。

根据 NODE_ENV 变量改变程序

有时候,我们可能会有一些特殊的要求,比如说不单单只有“开发环境”和“生产环境”,可能还会有一个“演示环境”,而“演示环境”它可能需要更换网页的一些文字、样式或者逻辑,相当于在程序的原基础上套上一层皮肤。

在这个时候,该怎么办?

现在我们可以利用上环境变量 NODE_ENV 了。

我们在根目录创建一个 config.js,这个 config.js 是用于接收命令的变量,并且根据变量去判断生产更多的变量,比如:在生产环境里,主页的头部要变为红色,脚部要变为黄色,那么我们就创建这两个变量出来,后面使用插件来根据这些变量去变颜色。

伪代码

if (process.env.NODE_ENV == 'production') {
    header = red
    footer = yellow
}
复制代码

当然,事实上你也可以不这么做,可以直接在业务代码里去根据变量做判断来改变颜色。

我们在 config.js 写如下代码。

const CONFIG = {}

CONFIG.ENV = process.env.NODE_ENV

module.exports = CONFIG
复制代码

注意:为了方便,这里暴露的属性名是 ENV,不是 NODE_ENV

在 gulpfile.js 引入 config.js。

const config = require('./config.js')
复制代码

改变 html

gulp-preprocess 是一个预处理 html 的插件,它可以在程序运行或打包前注入 html 的代码。

安装

npm i -D gulp-preprocess
复制代码

修改 gulpfile.js 配置,引入 gulp-preprocess。

const preprocess = require("gulp-preprocess")

...

function html() {
  return src(['src/**/*.html', '!src/include/**.html'])
    .pipe(changed('dist'))
    .pipe(plumber())
    .pipe(fileinclude({
      prefix: '@@', //引用符号
      basepath: './src/include', //引用文件路径
    }))
    .pipe(preprocess({
      context: { ...config }
    }))
    .pipe(htmlmin({
      removeComments: true, //清除HTML注释
      collapseWhitespace: true, //压缩HTML
      collapseBooleanAttributes: true, //省略布尔属性的值 <input checked="true"/> ==> <input />
      removeEmptyAttributes: true, //删除所有空格作属性值 <input id="" /> ==> <input />
      removeScriptTypeAttributes: true, //删除<script>的type="text/javascript"
      removeStyleLinkTypeAttributes: true, //删除<style>和<link>的type="text/css"
      minifyJS: true, //压缩页面JS
      minifyCSS: true //压缩页面CSS
    }))
    .pipe(dest('dist'))
}
复制代码

上面的 preprocess 的 context 里面传入 json 对象,这个 json 对象的各个变量会被引入到 html 里,这里用了 es6 的对象结构,将 config 对象的所有属性赋予到 context 里。

这个时候,我们可以用到 html 的变量,在 index.html 里,我们写入以下内容

<!-- @if ENV = 'production' -->
  <div><!-- @echo ENV --></div>
<!-- @endif -->
复制代码

这个代码意思是使用 @if 判断 ENV 变量是不是 production,如果是,则在 html 里写入 <div><!-- @echo ENV --></div>内容,而这里面的 <!-- @echo ENV --> 会输出 ENV 变量值 “production”。

注意:

  1. 这里的 <!-- --> 注释是要加上去的,不能去除,预处理器会自动匹配这样的代码。
  2. @if 的判断,如果是判断相等,用 =(只有一个等号),如果是不相等,用 !=

运行 npm run build,我们查看 dist/index.html,可以看到有 <div>production</div> 的相关内容。

image.png

运行 npm run dev,我们可以发现 dist/index.html 是没有上述内容的。

改变 js

不幸的是,gulp 似乎没有插件可以改变 js,gulp-preprocess 插件的文档是有提到可以在 js 上用,但经过我的试验,发现不行,有想法的小伙伴可以查阅下相关文档。

当然你也可以选择直接在 js 使用 process.env.NODE_ENV,但是这样之前写的 config.js 文件就没有任何意义了。

如果你的 gulp 项目有用到 webpack-stream,那么你可以装一个 webpack 的 preprocess-loader 来解决,该 loader 就是模仿 gulp-preprocess 而写的,只不过它似乎也只能用于 js。

安装

npm i -D preprocess-loader
复制代码

在 webpack.config.js 里配置

const config = require('./config')

...

module: {
  module: {
    rules: [
      ...
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: [
          {
            loader: 'preprocess-loader',
            options: {
              // 填写变量
              ...config,
              ppOptions: {
                type: 'js'
              }
            }
          }
        ]
      }
    ]
  },
  ...
}
复制代码

同理,这里也是引入 config.js,并且利用了 es6 的解构。

在 src/index.js 里,写上测试代码。

const env = '/* @echo ENV */'

if (env == 'production') {
  console.log(env)
}
复制代码

运行 npm run build,打开 dist/index.html 文件,再打开浏览器的控制台,可以发现,“production” 被打印出来了。

image.png

改变 scss

我们都知道 scss 是有变量的,现在我们需要根据环境改变这些 scss 变量值,gulp-sass-variables 插件可以做到这个效果。

安装

npm i -D gulp-sass-variables
复制代码

我们假设要根据不同环境去修改 cdn 地址,在开发环境里使用 http://2.com 前缀地址,在生产环境里使用 http://1.com的前缀地址。

我们修改下 config.js

const CONFIG = {}

CONFIG.ENV = process.env.NODE_ENV
CONFIG.CDN = ''

if (CONFIG.ENV == 'production') {
  CONFIG.CDN = 'http://1.com'
} else {
  CONFIG.CDN = 'http://2.com'
}

module.exports = CONFIG
复制代码

配置 gulpfile.js

const sassVariables = require('gulp-sass-variables')
const config = require('./config.js')

...

function scss() {
  return src(['src/scss/**/*.scss'])
    .pipe(changed('dist/scss/**/'))
    .pipe(plumber())
    .pipe(sassVariables({
      $CDN: config.CDN,
    }))
    .pipe(sass({ fiber }))
    .pipe(cleanCss())
    .pipe(dest('dist/scss'))
}
复制代码

注意,这里不能用 es6 的解构,因为 scss 的变量有规定变量名要有前缀 $

我们修改下 src/scss/index.scss 文件。

html {
  body {
    // background: blanchedalmond;
    background: url(#{$CDN}/simao.jpg);
  }
}
复制代码

上面使用 #{$CDN} 引入 CDN 变量。

运行 npm run build,可以发现 dist/scss/index.scss 文件写入了相应的 CDN 地址。

image.png

完整代码

目录

image.png

gulpfile.js

const { series, parallel, src, dest, watch } = require('gulp')

const Path = require('path')

const htmlmin = require('gulp-htmlmin')
const fileinclude = require('gulp-file-include')
const changed = require('gulp-changed')
const webserver = require('gulp-webserver')
const del = require('del')
const uglify = require('gulp-uglify')
const plumber = require('gulp-plumber')
const cleanCss = require('gulp-clean-css')
const sass = require('gulp-sass')
const fiber = require('fibers') // 变量名命名没 s
const imagemin = require('gulp-imagemin')
const cache = require('gulp-cache')
const webpack = require('webpack-stream')
const webpackConfig = require("./webpack.config.js")
const named = require('vinyl-named')
const path = require('path')
// const babel = require('gulp-babel')
const preprocess = require("gulp-preprocess")
const sassVariables = require('gulp-sass-variables')

const { js, libJs } = require('./gulpfile-js')

const config = require('./config.js')

sass.compiler = require('sass')
//sass.compiler = require('node-sass')

console.log('环境变量:' + process.env.NODE_ENV)

function html() {
  return src(['src/**/*.html', '!src/include/**.html'])
    .pipe(changed('dist'))
    .pipe(plumber())
    .pipe(fileinclude({
      prefix: '@@', //引用符号
      basepath: './src/include', //引用文件路径
    }))
    .pipe(preprocess({
      context: { ...config }
    }))
    .pipe(htmlmin({
      removeComments: true, //清除HTML注释
      collapseWhitespace: true, //压缩HTML
      collapseBooleanAttributes: true, //省略布尔属性的值 <input checked="true"/> ==> <input />
      removeEmptyAttributes: true, //删除所有空格作属性值 <input id="" /> ==> <input />
      removeScriptTypeAttributes: true, //删除<script>的type="text/javascript"
      removeStyleLinkTypeAttributes: true, //删除<style>和<link>的type="text/css"
      minifyJS: true, //压缩页面JS
      minifyCSS: true //压缩页面CSS
    }))
    .pipe(dest('dist'))
}

// function js() {
//   return src(['src/js/**/*.js'])
//     .pipe(changed('dist/js/**/'))
//     // .pipe(babel({
//     //   presets: ['@babel/env'],
//     //   plugins: ['@babel/transform-runtime']
//     // }))
//     .pipe(named(function (file) {
//       return file.relative.slice(0, -path.extname(file.path).length)
//     }))
//     .pipe(webpack(webpackConfig))
//     .pipe(plumber())
//     .pipe(uglify())
//     .pipe(dest('dist/js'))
// }

// function libJs() {
//   return src(['src/lib/**/*.js'])
//     .pipe(changed('dist/lib/**/'))
//     .pipe(plumber())
//     .pipe(dest('dist/lib'))
// }

function css() {
  return src(['src/css/**/*.css'])
    .pipe(changed('dist/css/**/'))
    .pipe(plumber())
    .pipe(cleanCss())
    .pipe(dest('dist/css'))
}

function scss() {
  return src(['src/scss/**/*.scss'])
    .pipe(changed('dist/scss/**/'))
    .pipe(plumber())
    .pipe(sassVariables({
      $CDN: config.CDN,
    }))
    .pipe(sass({ fiber }))
    .pipe(cleanCss())
    .pipe(dest('dist/scss'))
}

function libCss() {
  return src(['src/libCss/**/*.css'])
    .pipe(changed('dist/libCss/**/'))
    .pipe(plumber())
    .pipe(dest('dist/libCss'))
}

function img() {
  return src(['src/assets/img/**/*.{png,jpg,gif,jpeg,ico}']) //后缀都用小写,不然不识别
    .pipe(
      cache(
        imagemin({
          optimizationLevel: 5, //类型:Number  默认:3  取值范围:0-7(优化等级)
          progressive: true, //类型:Boolean 默认:false 无损压缩jpg图片
          interlaced: true, //类型:Boolean 默认:false 隔行扫描gif进行渲染
          multipass: true //类型:Boolean 默认:false 多次优化svg直到完全优化
        })
      )
    )
    .pipe(dest('dist/assets/img'))
}

function devServer() {
  return src('dist').pipe(webserver({
    port: 3000,
    livereload: true, // 是否实时加载
    // directoryListing: true, // 是否开启浏览目录
    // open: true, // 是否自动打开
    // proxies: [ // 代理,可以用来解决跨域问题
    //   {source: '/api', target: 'http://xxxx.com', options: {headers: {"Content-Type": 'application/x-www-form-urlencoded'}}}
    // ]
  }))
}

function watcher() {
  watch('src/**/*.html', series(html)).on('unlink', function (path) {
    del('dist/**/*.html' + Path.basename(path))
  })
  watch('src/js/**/*.js', series(js)).on('unlink', function (path) {
    del('dist/js/**/*.js' + Path.basename(path))
  })
  watch('src/lib/**/*.js', series(libJs)).on('unlink', function (path) {
    del('dist/lib/**/*.js' + Path.basename(path))
  })
  watch('src/css/**/*.css', series(css)).on('unlink', function (path) {
    del('dist/css/**/*.css' + Path.basename(path))
  })
  watch('src/scss/**/*.scss', series(scss)).on('unlink', function (path) {
    del('dist/scss/**/*.css' + Path.basename(path))
  })
  watch('src/libCss/**/*.css', series(libCss)).on('unlink', function (path) {
    del('dist/libCss/**/*.css' + Path.basename(path))
  })
  watch('src/assets/img/**/*.{png,jpg,gif,jpeg,ico}', series(img)).on('unlink', function (path) {
    del('dist/assets/img/**/*.{png,jpg,gif,jpeg,ico}' + Path.basename(path))
  })
}

function clean() {
  return del('dist')
}

exports.default = series(clean, html, libJs, js, css, scss, libCss, img, devServer, watcher)
exports.build = series(clean, html, libJs, js, css, scss, libCss, img)

复制代码

gulpfile-js.js

const { src, dest } = require('gulp')
const webpack = require('webpack-stream')
const webpackConfig = require("./webpack.config.js")
const named = require('vinyl-named')
const path = require('path')
const plumber = require('gulp-plumber')
const changed = require('gulp-changed')
const uglify = require('gulp-uglify')

function js() {
  return src(['src/js/**/*.js'])
    .pipe(changed('dist/js/**/'))
    // .pipe(babel({
    //   presets: ['@babel/env'],
    //   plugins: ['@babel/transform-runtime']
    // }))
    .pipe(named(function (file) {
      return file.relative.slice(0, -path.extname(file.path).length)
    }))
    .pipe(webpack(webpackConfig))
    .pipe(plumber())
    .pipe(uglify())
    .pipe(dest('dist/js'))
}

function libJs() {
  return src(['src/lib/**/*.js'])
    .pipe(changed('dist/lib/**/'))
    .pipe(plumber())
    .pipe(dest('dist/lib'))
}

module.exports = {
  js, libJs
}
复制代码

webpack.config.js

console.log('利用了 webpack !!!')

const path = require('path')
const config = require('./config')

module.exports = {
  mode: 'development',
  //devtool: 'eval-cheap-module-source-map',
  module: {
    rules: [
      {
        test: /\.m?js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
            plugins: ['@babel/plugin-proposal-object-rest-spread',
              '@babel/plugin-transform-runtime'],
            cacheDirectory: true,
          }
        }
      },
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: [
          {
            loader: 'preprocess-loader',
            options: {
              // 填写变量
              ...config,
              ppOptions: {
                type: 'js'
              }
            }
          }
        ]
      }
    ]
  },
  resolve: {
    alias: {

    }
  },
}
复制代码

config.js

const CONFIG = {}

CONFIG.ENV = process.env.NODE_ENV
CONFIG.CDN = ''

if (CONFIG.ENV == 'production') {
  CONFIG.CDN = 'http://1.com'
} else {
  CONFIG.CDN = 'http://2.com'
}

module.exports = CONFIG
复制代码

src/index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>首页</title>
  <link rel="stylesheet" href="./css/index.css">
  <link rel="stylesheet" href="./scss/index.css">
</head>
<body>
  @@include('./header.html', {"name": "hello")
  <div>首页内容</div>
  @@include('./footer.html')

  <!-- @if ENV = 'production' -->
  <div><!-- @echo ENV --></div>
  <!-- @endif -->

  <script src="./js/index.js"></script>
</body>
</html>
复制代码

src/js/index.js

function print() {
  console.log('测试')
}

print()

import utils from './utils'

utils.test()

const env = '/* @echo ENV */'

if (env == 'production') {
  console.log(env)
}

复制代码

src/scss/index.scss

html {
  body {
    // background: blanchedalmond;
    background: url(#{$CDN}/simao.jpg);
  }
}
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享