背景
在项目工程化中,一般根据不一样的环境对项目进行构建,通常环境主要有开发、测试、预生产、生产。开发环境下关注规范、测试,而其余环境主要关注性能、安全,好比开发环境下进行eslint检查,而其余环境不作eslint检查。css
项目相关依赖html
cnpm i -D cross-env gulp-if gulp-eslint babel-eslint gulp-sourcemaps exorcist
exorcist
用于在外部生成map
文件
目录结构node
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执行脚本react
"scripts": { "dev": "cross-env NODE_ENV=dev gulp dev", "build": "cross-env NODE_ENV=prod gulp build" },
脚本git
// index.js import './src/App.jsx' // src/App.jsx import React from 'react' import { render } from 'react-dom' import Test from './components/Test/index.jsx' render(<Test />, document.getElementById("app")) // src\components\Test\index.jsx import 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.jsx import 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.jsx import React from 'react' const Child = (props) => <div className="color">{props.msg}</div> export default Child // src\components\Test\index.css .color { color: red; }
构建脚本github
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 || 8080 const NODE_ENV = process.env.NODE_ENV const 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配置npm
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
语法。json
开发环境构建结果gulp
生产环境构建结果sass