Self made scaffold

After learning the basis of Webpack, you will always be confused when you check the scaffold written by others. Later, you can check all kinds of materials on the Internet, modify them while referring to them, and complete a set of simple Scaffolding (uploaded to GiuHub and npm I have learned from Create React App (CRA) directory structure (as shown below), and made Command line tools (uploaded to GiuHub and npm On).

├ - pwu
 ├ - config -------------- webpack configuration directory
 Configuration directory of jet test
 │├├├--- webpack.base.config.js -------- general configuration
 │├├├─ webpack.dev.config.js -------- development environment configuration
 │├├├--- webpack.prod.config.js -------- production environment configuration
 │├ - bin -------- command line tool
 │├├ - pwu.js -------- command file
 │├ - dist ------------ output directory
 │├├ - css
 Image
 │├├ - js ------------ script
 │├ - public -------------- template directory
 │├├├ - index.html ------------ template page
 │├ - src -------- --- source file directory
 Test catalog
 Components
 ├├├ - font
 Image catalog
 ├├├ - index.js -------- entry file
 │├├├ - index.scss ------------ global style
 ├├ - package.json ------------- manage dependent packages
 Version number and source of management package
 │├ - postcss.config.js -------- post processor configuration file
 │ ├ -- tsconfig.json ------------- typescript configuration file
 │ ├ -. Eslintrc -------------- eslint configuration file
 │├├. Eslintignore ------------- files and directories ignored by eslint
 ├├. Gitignore -------------- files and directories ignored by Git

1, General configuration

1) Entrance and exit

In a generic configuration, you include parameters that are required for both environments, such as the entry and exit, as shown below. path Is the path module in Node.js path.resolve() Used to resolve absolute paths, __dirname Can read the directory name of the current module.

const path = require("path");
module.exports = {
  entry: {
    index: "./src/index.js"
  },
  output: {
    path: path.resolve(__dirname, "../dist"),
    publicPath: "/"
  }
};

  publicPath Specify the base path of the static resource as follows.

Final path of static resource = output.publicPath + configuration path of loader or plug-in.

Assuming that the background of html element is a relative path, the final generated path will be "/ img/lake.png", where the configured output directory is "img".

html {
  background: url("../../../public/img/lake.png") no-repeat;
}
/* Generated background path */
html {
  background: url("/img/lake.png") no-repeat;
}

In the webpack.config.js configuration file of CRA, there is also the configuration of publicPath. As shown below, the production and development environment will have corresponding values.

const publicPath = isEnvProduction ? paths.servedPath : isEnvDevelopment && '/';

2) Loader

In the loader, the script is added( babel-loader ), style( css-loader,postcss-loader and sass-loader ), image( url-loader )And fonts( file-loader).

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: "babel-loader",
        exclude: /node_modules/
      },
      {
        test: /\.(sass|scss)$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          "postcss-loader",
          "sass-loader"
        ]
      },
      {
        test: /\.(jpg|png|gif)$/,
        use: {
          loader: "url-loader",
          options: {
            name: "[name].[ext]",
            outputPath: "img/",
            limit: 8192
          }
        }
      },
      {
        test: /\.(eot|ttf|svg|woff|woff2)$/,
        use: {
          loader: "file-loader",
          options: {
            name: "[name]_[hash].[ext]",
            outputPath: "font/"
          }
        }
      }
    ]
  }
};

In the configuration of parsing style, four loaders are used, and the latter is executed first. Babel The configuration information of is written to the package.json file, and a new babel field is created. The value of useBuiltIns is usage, which means the Polyfill required for auto loading the source code.

"babel": {
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "ie": 11,
          "chrome": 49
        },
        "corejs": "2",
        "useBuiltIns": "usage"
      }
    ],
    "@babel/preset-react"
  ]
}

Postcss loader, also known as CSS postprocessor, is often used to improve browser compatibility. It has many supporting plug-ins (such as autofix ), the configuration of these plug-ins is placed in a separate postcss.config.js file, as shown below.

module.exports = {
  plugins: [require("autoprefixer")()]
};

During execution, postcss loader will suggest that you put the browser information in package.json and create a new browser list field, as shown below.

"browserslist": [
  "last 5 version",
  ">1%",
  "ie >=8"
]

MiniCssExtractPlugin.loader refers to mini-css-extract-plugin The loader of the plug-in. The plug-in can extract CSS styles from JS files and save them in a separate CSS file.

The outputPath property in URL loader and file loader is used to configure the output directory. The value of the limit attribute in the image is 8192, in bytes, equivalent to 8kb. If the image size is smaller than this value, it will be converted to Base64 format, embedded in the file, and reduce HTTP requests. The name of the font file is also added with a unique hash value, as shown below.

iconfont_7346d960c4ad96f1ea8d5a8834fab00f.ttf

3) Plug in

The function of MiniCssExtractPlugin plug-in has been mentioned before, and the chunkFilename parameter will be used in dynamic import.

plugins: [
  new MiniCssExtractPlugin({
    filename: "css/[name].[hash].css",
    chunkFilename: "css/[id].[hash].css"
  })
]

2, Development environment configuration

In the development environment, it is necessary to introduce general configuration and reuse webpack-merge Merge as follows. mode Field is used to tell webpack to use the optimization of the corresponding pattern. The output file name also contains a hash, but only the first eight characters are extracted.

const base = require('./webpack.base.config.js');
const merge = require('webpack-merge');
module.exports = merge(base, {
  mode: "development",
  output: {
    filename: "js/[name].[hash:8].bundle.js"
  }
});

1)webpack-dev-server

Turn on the local server based on Node.js: webpack-dev-server.

devServer: {
  contentBase: path.resolve(__dirname, "../dist"),
  open: true,            //Open browser automatically
  port: 4000,            //Port number
  compress: true,        //Enable gzip Compression:
  useLocalIp: true,      //Using the machine IP
  hot: true              //Turn on hot update
}

2)Source Map

Trace the original location of the error or warning in the source file through the Source Map for debugging and configuration devtool Implementation, as shown below.

devtool: "source-map"

Add webback's HotModuleReplacementPlugin Plug in, as shown below.

const webpack = require('webpack');
module.exports = merge(base, {
  plugins: [
    new webpack.HotModuleReplacementPlugin()
  ]
});

3)HtmlWebpackPlugin

  HtmlWebpackPlugin The plug-in can generate an HTML file according to the template, and can automatically import the required bundle file. The template file is placed in the public directory, as shown below.

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8"/>
    <title>Scaffold example</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

The specific configuration is as follows, inject parameter Specifies the script injection location, such as the bottom of the body element.

const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = merge(base, {
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "../public/index.html"),
      inject: "body"
    })
  ]
});

4) Script command

In the scripts field of the package.js file, the start command is declared, the local server is opened, and the script is overloaded in real time.

{
  "scripts": {
    "start": "webpack-dev-server --config ./config/webpack.dev.config.js"
  }
}

3, Production environment configuration

Production environment pays more attention to performance, so it needs to do a lot of optimization configuration, such as compression, code separation, etc. mode adopts production optimization mode, as shown below.

module.exports = merge(base, {
  mode: "production",
  output: {
    filename: 'js/[name].[chunkhash:8].bundle.js'
  }
}

1)optimization

First of all, code separation is optimized, that is, extracting stable modules (such as react, react DOM, etc.) into a separate file. The configuration of splitChunks parameter can be referred to SplitChunksPlugin plug-in unit.

module.exports = merge(base, {
  optimization: {
    splitChunks: {
      chunks: "all",
      minSize: 30000,
      maxSize: 0,
      minChunks: 1,
      cacheGroups: {
        vendors: {
          test: /node_modules/,
          name: "vendor",
          enforce: true
        }
      }
    }
  }
});

cacheGroups is the key to optimization. It is a cache group (properties are shown below). vendors will filter the modules introduced from the node Ou modules directory.

(1) test: a string, regular or function, matching condition of the module.

(2) Name: the name of the chunk split.

(3) enforce: when true, the minSize, minChunks, maxAsyncRequests, and maxInitialRequests options can be ignored.

(4) priority: the priority of packaging.

The next optimization is compression, which is configured into the optimizer option. UglifyjsWebpackPlugin The plug-in uses UglifyJS to compress JavaScript code. OptimizeCssAssetsPlugin The plug-in is used to compress CSS files.

const UglifyjsWebpackPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = merge(base, {
  optimization: {
    minimizer: [
      new UglifyjsWebpackPlugin(),
      new OptimizeCssAssetsPlugin({
        assetNameRegExp: /\.css$/g,
        cssProcessor: require("cssnano"),
        cssProcessorPluginOptions: {
          preset: ["default", { discardComments: { removeAll: true } }]
        },
        canPrint: true
      })
    ]
  }
});

2) Plug in

The production environment also requires template plug-ins, just configure the minify option, as shown below, removing comments and spaces.

new HtmlWebpackPlugin({
  template: path.resolve(__dirname, "../public/index.html"),
  inject: "body",
  minify: {
    removeComments: true,
    collapseWhitespace: true
  }
})

  CleanWebpackPlugin The plug-in cleans up files in the output directory.

const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = merge(base, {
  plugins: [
    new CleanWebpackPlugin()
  ]
});

Occasionally, the error in Figure 45 appears, and the cause has not been found yet.

Figure 45

3) Script command

At package.js In the scripts field of the file, add the build command to build the project locally.

{
  "scripts": {
    "start": "webpack-dev-server --config ./config/webpack.dev.config.js",
    "build": "webpack --config ./config/webpack.prod.config.js"
  }
}

4, TypeScript

To support TypeScript , the corresponding module and loader must be installed. The command is as follows.

npm install --save-dev typescript ts-loader

In the general configuration of webpack, add the following fields, resolve The extensions property of can be introduced without extensions.

module.exports = {
  resolve: {
    extensions: [".tsx", ".ts", ".js"]
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: [ 'ts-loader' ],
        exclude: /node_modules/
      }
    ]
  }
};

You have to add tsconfig.json Configuration file, as shown below, the specific field description can be Refer to official documents.

{
  "compilerOptions": {
    "outDir": "./dist/",
    "noImplicitAny": true,
    "module": "es6",
    "target": "es5",
    "jsx": "react",
    "allowJs": true
  }
}

Because you want to use react and react DOM, you also need to install their declaration files: @types/react and @types/react-dom . And used the HTML webpack plugin plug-in, its declaration file( @types/html-webpack-plugin )It also has to be installed.

After all are installed, you can use JSX syntax in the tsx file, as shown below.

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import App from './component/app/app';

function init() {
  ReactDOM.render(React.createElement(App, null), document.getElementById('root'));
}
init();

In the general configuration in webpack, you can add a new entry file, as shown below.

module.exports = {
  entry: {
    index: "./src/index.ts",
    index2: "./src/index.tsx"
  }
}

5, ESLint

  ESLint It is a popular static code detection tool. It can establish a set of code specifications, ensure the consistency of code, and avoid unnecessary errors.

1) Basic configuration

Installation is required first ESLint And ESLint's loader , the command is as follows.

npm install --save-dev eslint eslint-loader

Then add the eslint loader to the generic configuration, as shown below.

module.exports = {
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: [ 'babel-loader', 'eslint-loader'] ,
        exclude: /node_modules/
      },
      {
        test: /\.tsx?$/,
        use: [ 'ts-loader', 'eslint-loader' ],
        exclude: /node_modules/
      }
    ]
  }
};

The. eslintrc configuration file is then created in the root directory, as shown below, and various rules can be recorded in the rules field.

{
  "rules": {
  }
}

2) Rules

Running the scaffold now reports an error (as shown below) because ESLint does not recognize ES6 syntax.

1:1  error  Parsing error: The keyword 'import' is reserved

To avoid this error, install babel-eslint , and modify the. eslintrc file.

{
  "parser": "babel-eslint",
  "rules": {
  }
}

Add a simple max-len Rules (other rules can refer to Official documents ), the maximum length of a line is 200, and the width of four Tab characters is ignored.

{
  "rules": {
    "max-len": ["warn", 200, 4, { "ignoreComments": true }]
  }
}

When the limit is exceeded, the following warning is displayed.

7:1  warning  This line has a length of 292. Maximum allowed is 200  max-len

Because React is used, you can also add React rules and install eslint-plugin-react , and modify the. eslintrc file.

{
  "plugins": [
    "react"
  ]
}

If you don't want to define your own rules, you can directly use open source rules on the Internet, such as Airbnb Of JavaScript coding specification . Note that Airbnb's standard package relies on eslint-plugin-import , eslint plugin react and eslint-plugin-jsx-a11y Etc. After the installation is successful, modify the. eslintrc file again.

{
  "extends": "airbnb"
}

When you rerun the scaffold, you'll get a lot of errors and warnings, modify the loader (as shown below), and use the -- fix parameter to reduce them a lot.

module.exports = {
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: [
          'babel-loader', 
          {loader: 'eslint-loader', options: {fix: true}} 
        ],
        exclude: /node_modules/
      },
      {
        test: /\.tsx?$/,
        use: [
          'ts-loader',
          {loader: 'eslint-loader', options: {fix: true}}
        ],
        exclude: /node_modules/
      }
    ]
  }
};

3)pre-commit

If the version control system used is Git, you can detect the rules of ESLint before each commit. When the detection fails, the submission can be blocked.

  husky It is a Git hook tool, which can prevent bad git commit, git push and other operations. lint-staged The specified task can be performed on the staged Git file. Note that husky has requirements for Node and Git versions, the former is greater than 10, and the latter is greater than two point one three .

Next change package.json File, add husky and lint staged fields, configure ESLint detection in lint staged and the file suffix to be detected. When the detection fails, you will get the prompt in Figure 46.

"husky": {
  "hooks": {
    "pre-commit": "lint-staged"
  }
},
"lint-staged": {
  "*.{js,jsx,ts,tsx}": [
    "eslint"
  ]
}

Figure 46

6, Jest

Jest is a test framework of Facebook open source, and once wrote an article Getting Started tutorial . To integrate jet into Webpack First, install Jest , after installation package.json Add a script command to the file (shown below), execute Jest, and print test coverage. Note that the generated test coverage information is saved to the coverage directory by default.

"scripts": {
  "test": "jest --coverage"
}

Now execute "npm test", there will be no result, because the test script has not been written. Jet will test by default__ tests__ The directory and name contain script files (including TypeScript files) for spec or test, and node is ignored by default_ The configuration items of the files in the modules directory are as follows.

testMatch: [ '**/__tests__/**/*.js?(x)', '**/?(*.)(spec|test).js?(x)' ]
testPathIgnorePatterns: ["node_modules"]

Add in src directory__ tests__ Directory, and create a new app.js , the code is as follows, adding a test case for demonstration.

describe("my test case", () => {
  test("one plus one is two", () => {
    expect(1 + 1).toBe(2);
  });
});

When ES6 syntax is used in the test case (for example, introduce components as follows), an error will be prompted, which needs to be introduced babel-jest . Babel Jest is automatically downloaded when you install Jest, so you don't have to install it separately.

import { App } from '../component/app/app';

At package.json File defines the jet field and declares transform Option, add the following rule to avoid error reporting.

"jest": {
  "transform": {
    "^.+\\.js$": "babel-jest"
  }
}

Jest has a number of other configurations that can play a significant role in testing. For example, when using style objects, return all classnames as is (for example styles.container ==='container'), which facilitates snapshot testing. To do this, install identity-obj-proxy , and modify the moduleNameMapper option as shown below.

"jest": {
  "moduleNameMapper": {
    "\\.(css|scss)$": "identity-obj-proxy"
  }
}

When the modulenamapper cannot meet the requirements, you can use the transform option to set the conversion rules, as shown below.

"jest": {
  "transform": {
    "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2)$": "<rootDir>/config/jest/fileTransformer.js"
  }
}

   fileTransformer.js The file is located in the jet directory of the configuration directory, and its function is to return the name of the file (as shown in the following code), such as require('avatar.png ') return“ avatar.png ”.

const path = require('path');
module.exports = {
  process(src, filename, config, options) {
    return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';';
  }
};

Note that ESLint has been used to detect code before, so test cases will be detected as well. If you don't want to execute ESLint, you can add the. eslintignore file as shown below, where the configuration directory is ignored.

src/__tests__
config

7, Command line tools

I wrote a command line tool API Tutorial . The current idea is to separate the command-line tool from the scaffold and download the scaffold by command.

Install first ora,chalk,commander and download-git-repo Four packages, the installation command is as follows.

npm install --save ora chalk commander download-git-repo

ora is an elegant terminal spinner, chalk can add color to the text in the terminal, commander is a tool for editing commands, Download git repo can download the warehouse code on GitHub. Here are the specific commands( pwu-cli )It has been uploaded to npm. After the installation is successful, you can execute "pwu create demo" to create the demo directory (as shown in Figure 47), and download it automatically pwu Scaffold code in warehouse.

#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const ora = require('ora');
const chalk = require('chalk');
const program = require('commander');
const download = require('download-git-repo');

program
    .version('1.0.0', '-v, --version', 'edition');
program
  .command('create <name>')
  .description('create a repository')
  .action(name => {
    const spinner = ora('Start downloading scaffolding');
    spinner.start();
    const destination = path.join(process.cwd(), name);
    if(fs.existsSync(destination)) {
      console.log(chalk.red('Scaffold already exists'));
      return;
    }
    download('github:pwstrick/pwu', destination, (err) => {
      spinner.stop();                    
      console.log(chalk.green('Scaffold download succeeded'));
    });
  });
program.parse(process.argv)

Figure 47

When publishing to npm, npm can filter according to the content in the. gitignore file, so as to avoid uploading dependent modules.

 

reference material:

React & Webpack

Detailed explanation of publicPath in Webpack

Mini CSS extract plugin quick start

Configure webpack 4+react scaffold from zero

What is the Purpose of chunkFilename of mini-css-extract-plugin Module?

sass image address

Split chunks configuration of webpack 4 Code Splitting

webpack SplitChunksPlugin Practical Guide

Self made front-end scaffold

Using Node.js to develop a simple scaffold tool

Using ESLint in the React+Babel+Webpack environment

Using husky + lint staged + prettier to optimize code format

husky and lint staged run lint automatically before git commit

jest works with Webpack

jest tutorial

Tags: Webpack React git npm

Posted on Mon, 18 May 2020 22:56:33 -0400 by Vertical3