背景
在项目工程化中,通常根据不同的环境对项目进行构建,一般环境主要有开发、测试、预生产、生产。开发环境下关注规范、测试,而其他环境主要关注性能、安全,比如开发环境下进行eslint检查,而其他环境不做eslint检查。

项目相关依赖

cnpm i -D cross-env gulp-if gulp-eslint babel-eslint gulp-sourcemaps exorcist

exorcist用于在外部生成map文件
目录结构

gulp├── src│   ├── components│   │   ├── Test│   │   |   └── index.jsx│   │   ├── Child│   │   |   ├── index.css│   │   |   └── index.jsx│   │   ├── Count│   │   |   ├── index.css│   │   |   └── index.jsx│       └── App.jsx ├── node_modules├── .eslintignore├── .eslintrc.js├── index.js├── gulpfile.js├── index.html└── package.json

package.json执行脚本

"scripts": {    "dev": "cross-env NODE_ENV=dev gulp dev",    "build": "cross-env NODE_ENV=prod gulp build"  },

脚本

// index.jsimport './src/App.jsx'// src/App.jsximport React from 'react'import { render } from 'react-dom'import Test from './components/Test/index.jsx'render(<Test />, document.getElementById("app"))// src\components\Test\index.jsximport React from 'react'import Child from '../Child/index.jsx'import Count from '../Count/index.jsx'export default class Test extends React.Component {  state = {    msg: 'hello, world'  }  render() {    const { msg } = this.state    return (      <React.Fragment>        <Child msg={msg}/>        <Count />      </React.Fragment>    )  }}// src\components\Count\index.jsximport React from 'react'export default class Count extends React.Component {  state = {    count: 0,    msg: '显示一个计数器'  }  subFn = () => {    this.setState((prevState, props) => ({      count: prevState.count - 1    }))  }  plusFn = () => {    this.setState((prevState, props) => ({      count: prevState.count + 1    }))  }  render() {    const { count, msg } = this.state    return (      <div>        <p className="count-text">{msg}</p>        <div>          <button onClick={this.subFn}>-</button>          <span>{count}</span>          <button onClick={this.plusFn}>+</button>        </div>      </div>    )  }}// src\components\Count\index.css.count-text {  font-size: 14px;  font-family: Arial, Helvetica, sans-serif;  font-weight: 400;  color: red;}// src\components\Test\index.jsximport React from 'react'const Child = (props) => <div className="color">{props.msg}</div>export default Child// src\components\Test\index.css.color {  color: red;}

构建脚本

const gulp = require('gulp')const path = require('path')const babelify = require('babelify')const browserify = require('browserify')    // 转成stream流,gulp系const source = require('vinyl-source-stream')    // 转成二进制流,gulp系const buffer = require('vinyl-buffer')const { series, parallel } = require('gulp')const rm = require('rimraf')const concat = require('gulp-concat')const sass = require('gulp-sass')const cheerio = require('gulp-cheerio')const uglify = require('gulp-uglify')const cssmin = require('gulp-cssmin')const minifyHtml = require('gulp-minify-html')const rename = require('gulp-rename')const resversion = require('gulp-res-version')const connect = require('gulp-connect')const gulpif = require('gulp-if')const eslint = require('gulp-eslint')const sourcemaps = require('gulp-sourcemaps')const exorcist = require('exorcist')const PORT = process.env.PORT || 8080const NODE_ENV = process.env.NODE_ENVconst isDev = NODE_ENV === 'dev'const isProd = NODE_ENV === 'prod'const _path = {  main_js: './index.js',  js: ['./index.js', './src/*.jsx', './src/**/**/*.jsx'],  lint_js: ['./src/*.jsx', './src/**/**/*.jsx'],  scss: ['./src/components/**/*.scss'],  html: './index.html'}const clean = (done) => {  rm('./dist', error => {    if (error) throw error    done()  })}const _css = () => {  return gulp.src(_path.scss)    .pipe(sourcemaps.init())    .pipe(sass())    .pipe(concat('app.css'))    .pipe(gulpif(isProd, cssmin()))    .pipe(gulpif(isProd, rename({suffix: '.min'})))    .pipe(gulpif(isDev, sourcemaps.write('/')))    .pipe(gulp.dest('./dist/css'))    .pipe(gulpif(isDev, connect.reload()))}const _lint = () => {  return gulp.src(_path.lint_js)    .pipe(eslint({      useEslintrc: true    }))    .pipe(eslint.format())    .pipe(eslint.failAfterError())}const _script = () => {  return browserify({      entries: _path.main_js,      debug: true, // 生成inline-sourcemap      transform: [babelify.configure({        presets: ['@babel/preset-env', '@babel/preset-react'],        plugins: [          '@babel/plugin-transform-runtime',           ["@babel/plugin-proposal-decorators", { "legacy": true }],           ["@babel/plugin-proposal-class-properties", { "loose": true }]        ]      })]    })    .bundle()    .pipe(gulpif(isDev, exorcist(path.resolve(__dirname, `dist/js/app${isDev ? '' : '.min'}.js.map`))))    .pipe(source('app.js'))    .pipe(buffer())    .pipe(gulpif(isProd, uglify()))    .pipe(gulpif(isProd, rename({suffix: '.min'})))    .pipe(gulp.dest('./dist/js'))    .pipe(gulpif(isDev, connect.reload()))}const _html = () => {  return gulp.src(_path.html)  .pipe(cheerio(($ => {    $('script').remove()    $('link').remove()    $('body').append(`<script src="./js/app${isDev ? '' : '.min'}.js"></script>`)    $('head').append(`<link rel="stylesheet" href="./css/app${isDev ? '' : '.min'}.css">`)  })))  .pipe(gulpif(isProd, minifyHtml({    empty: true,    spare: true  })))  .pipe(gulp.dest('./dist'))  .pipe(gulpif(isDev, connect.reload()))}const _rev = () => {  return gulp.src('./dist/*.html')    .pipe(resversion({      rootdir: './dist/',      ignore: [/#data$/i]    }))    .pipe(gulp.dest('./dist'))}const _server = () => {  connect.server({    root: 'dist',    port: PORT,    livereload: true  })}const _watch = () => {  gulp.watch(_path.html, _html)  gulp.watch(_path.js, _script)  gulp.watch(_path.scss, _css)}module.exports = {  build: series(clean, parallel(_script, _css, _html), _rev),  dev: series(clean, _lint, parallel(_script, _css, _html), _rev, parallel(_server, _watch))}

.eslintrc.js配置

module.exports = {    "env": {        "browser": true,        "es2020": true,        "node": true    },    "extends": [        "eslint:recommended",        "plugin:react/recommended"    ],    // 开发环境与ESLint当前的解析功能不兼容    "parser": "babel-eslint",    "parserOptions": {        "ecmaFeatures": {            "experimentalObjectRestSpread": true, // 启用实验室特性            "jsx": true        },        "ecmaVersion": 11,        "sourceType": "module"    },    "plugins": [        "react"    ],    "rules": {        // "no-undef": "off",        "react/prop-types": "off",        "no-unused-vars": "off"    }};

注意:如果eslint解析时报了下面错误,请指定eslint解析器为babel-eslint,因为eslint默认解析器Espree不支持ES7语法。

开发环境构建结果

生产环境构建结果