Ah, sub is really uncomfortable ~ but let’s continue!

Support the React

Install React and react-dom:

npm install react react-dom -S
Copy the code

-s equals –save, -d equals –save-dev.

To use the JSX syntax, type SRC /index.js:

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'

ReactDOM.render(<App />.document.querySelector('#root'))
Copy the code

Enter the following sample code in SRC /app.js:

import React from 'react'

function App() {
  return <div className='App'>Hello World</div>
}

export default App
Copy the code

Then modify the entry field in webpack.common.js and change the entry file to index.js:

module.exports = {
  entry: {
+ app: resolve(PROJECT_PATH, './src/index.js'),
- app: resolve(PROJECT_PATH, './src/app.js'),}},Copy the code

If you try NPM run start or NPM run build, you will get an error:

Either! I already have react installed. What if WebPack doesn’t recognize JSX syntax at all? The file is preprocessed using babel-loader.

I strongly encourage you to read a very good article about Babel: I know it’s annoying to insert a link into your post and tell the reader to read it and then come back to it. I feel the same way when I read other people’s posts, but I really recommend it. Be sure to read! Be sure to read!

Ok, install the required packages:

npm install babel-loader @babel/core @babel/preset-react -D
Copy the code

Babel-loader parses files using Babel; @babel/core is the core module of Babel; @babel/preset- React paraphrase JSX syntax.

To create a new.babelrc file in the root directory, enter the following code:

{
  "presets": ["@babel/preset-react"]}Copy the code

Presets are a series of plug-in collections. For example, @babel/preset-react includes @babel/plugin-syntax-jsx, @babel/plugin-transform-react-jsx, @babel/plugin-transform-react-display-name

Next open our webpack.common.js file and add the following code:

module.exports = {
	// other...
  module: {
    rules: [{test: /\.(tsx? |js)$/,
        loader: 'babel-loader'.options: { cacheDirectory: true },
        exclude: /node_modules/,},// other...]},plugins: [ / /... ] .
}

Copy the code

JSX. JSX. JSX. JSX. JSX. JSX.

CacheDirectory is used to cache these public files, which will make the next compilation much faster.

You are advised to specify either include or exclude for the loader, because the node_modules directory does not need to be compiled. Excluding the node_modules directory can effectively improve compilation efficiency.

Now we can NPM run start to see the effect! Babel has some other important configuration, let’s get TS support and work on it together!

Support the TypeScript

The Webpack module system can only recognize JS files and their syntax. When it comes to JSX syntax, TSX syntax, files, images, fonts, etc., it needs the corresponding Loader to preprocess them. We used babel-Loader and the plugins that we now need to support TypeScript.

1. Install the Babel plug-in

@babel/preset-typescript is a preset of Babel, it’s rough to build TS, it just strips out the TYPE declaration of TS and builds with other Babel plug-ins, so it’s fast.

How much nonsense, first to install it:

npm install @babel/preset-typescript -D
Copy the code

Note: We didn’t need to install Typescript in the Eslint configuration, so we didn’t need to install Typescript again.

Then modify.babelrc:

{
  "presets": ["@babel/preset-react"."@babel/preset-typescript"]}Copy the code

Presets are executed from back to front. Based on the Babel configuration of the above code, @babel/preset-typescript is executed first, and then @babel/preset- React.

2. TSX grammar test

SRC/has the following two.tsx files with the following code:

Index. TSX:

import React from 'react'
import ReactDOM from 'react-dom'
import App from './app'

ReactDOM.render(
  <App name='vortesnail' age={25} />.document.querySelector('#root'))Copy the code

App. TSX:

import React from 'react'

interface IProps {
  name: string
  age: number
}

function App(props: IProps) {
  const { name, age } = props
  return (
    <div className='app'>
      <span>{`Hello! I'm ${name}, ${age} years old.`}</span>
    </div>)}export default App
Copy the code

Very simple code, in
when entering properties because ts has a good intelligent prompt, such as you do not enter name and age, then will report an error, because in
components, these two properties must be values!

If you run the webpack.common.js file, run the webpack.common.js file.

module.exports = {
  entry: {
    app: resolve(PROJECT_PATH, './src/index.tsx'),},output: {/ /... },
  resolve: {
    extensions: ['.tsx'.'.ts'.'.js'.'.json',}}Copy the code

Change the suffix of the entry file to.tsx.

Add the resolve attribute to import a file from the Extensions folder, as shown in the preceding code:

import App from './app'
Copy the code

You don’t have to add file suffixes. [‘.tsx’, ‘.ts’, ‘.js’, ‘.json’]. Webpack tries to add the.tsx suffix to the file. If it can’t find the file, it tries to find it. Therefore, we try to put the most commonly used suffixes at the front of the configuration, which can shorten the search time.

At this point, the NPM run start will output the page correctly.

With Typescript, React type declarations are required. Install them:

npm install @types/react @types/react-dom -D
Copy the code

3. Tsconfig. Json explanation

Every Typescript has a tsconfig.json file that simply says:

  • Compiles the specified file
  • Defines compilation options

The tsconfig.json file is typically placed in the project root directory. Type the following code on the console to generate this file:

npx tsc --init
Copy the code

Open the generated tsconfig.json, there are a lot of comments and a few configurations, it’s a bit messy, let’s delete the contents of this file and re-enter our own configuration.

The code in this file now reads:

{
  "compilerOptions": {
    // Basic configuration
    "target": "ES5".// to which version of es is compiled
    "module": "ESNext".// Specify which module system code to generate
    "lib": ["dom"."dom.iterable"."esnext"].// A list of library files that need to be imported during compilation
    "allowJs": true.// Allow compiling js files
    "jsx": "react".JSX is supported in.tsx files
    "isolatedModules": true."strict": true.// Enable all strict type checking options

    // Module parsing options
    "moduleResolution": "node".// Specify the module resolution policy
    "esModuleInterop": true.Support interoperability between CommonJS and ES modules
    "resolveJsonModule": true.// Support to import JSON modules
    "baseUrl": ". /"./ / root path
    "paths": {																// Pathmap, associated with baseUrl
      "Src/*": ["src/*"]."Components/*": ["src/components/*"]."Utils/*": ["src/utils/*"]},// Experimental options
    "experimentalDecorators": true.// Enable experimental ES decorator
    "emitDecoratorMetadata": true.// Add design-type metadata to the decorator declaration in the source code

    // Other options
    "forceConsistentCasingInFileNames": true.Disallow inconsistent references to the same file
    "skipLibCheck": true.// Ignore type checking for all declaration files (*.d.ts)
    "allowSyntheticDefaultImports": true.// Allow default imports from modules that do not have default exports set
    "noEmit": true														// You only want to use TSC's type checking as a function (when other tools (such as Babel actually compile) use it)
  },
  "exclude": ["node_modules"]}Copy the code

CompilerOptions is used to configure compilation options. The full configurable fields can be queried from here. Exclude specifies files that do not need to be compiled. In this case, node_modules will not be compiled. Of course, you can also use include to specify files that need to be compiled.

Here’s a quick explanation for important compilerOptions:

  • Target and the module: These two parameters are actually useless, they are executed through the TSC command to generate the corresponding ES5 version of THE JS syntax, but in fact we already compiled our TS syntax using Babel, we don’t use the TSC command at all, so their purpose here is to get the editor to provide error messages.

  • IsolatedModules: You can provide some additional syntax checking.

For example, export cannot be repeated:

import { add } from './utils'
add()

export { add } / / complains
Copy the code

For example, each file must be a separate module:

const print = (str: string) = > { console.log(str) } // An error is reported, no module is exported

// There must be export
export print = (str: string) = > { 
  console.log(str) 
}
Copy the code
  • esModuleInterop: allows us to import CommonJS modules that conform to the ES6 module specification.

For example, if a package is test.js:

// node_modules/test/index.js
exports = test
Copy the code

Using this package:

// App.tsx in our project
import * as test from 'test'
test()
Copy the code

After esModuleInterop is enabled, you can use the following commands:

import test from 'test'
test()
Copy the code

Let’s focus on baseUrl and Paths. These two configurations are really powerful tools for improving development efficiency. Its purpose is to quickly locate a file and prevent multiple layers of… /.. /.. / This way to find a module! For example, I now have several files under SRC / :

I’m going to introduce the Header component under SRC/Components in app.js as follows:

import Header from './components/Header'
Copy the code

You might think, well, that’s fine, that’s fine. But I’m here because app. TSX and Components are on the same level, so imagine if you wanted to use Components in some deep file, it would be crazy.. /.. /.. /.. So we’ll learn to use it and use it in combination with Webpack’s resolve.alias to make it more fragrant.

But want to use it is quite a lot of trouble, let’s take it apart step by step.

First, baseUrl must be set correctly. Our tsconfig.json is placed in the project root directory, so baseUrl is set to./ to represent the project root path. Thus, each path map in Paths, such as [” SRC /*”], is a relative root path.

If you are configured as above, try the following for yourself:

import Header from 'Components/Header'
Copy the code

Because of ESLint, errors are reported:

Eslint-import-resolver-typescript: eslint-import-resolver-typescript: eslint-import-resolver-typescript

npm install eslint-import-resolver-typescript -D
Copy the code

Then change the setting field in the.eslintrc.js file to the following code:

settings: {
  'import/resolver': {
    node: {
      extensions: ['.tsx', '.ts', '.js', '.json'],
    },
    typescript: {},
  },
},
Copy the code

Yes, just add typescript: {} and no errors will be reported. But all we’ve done above is recognize the path map for the editor. We need to add the same mapping rule configuration to resolve. Alias in webpack.common.js:

module.exports = {
  // other...
  resolve: {
    extensions: ['.tsx'.'.ts'.'.js'.'.json'].alias: {
      'Src': resolve(PROJECT_PATH, './src'),
      'Components': resolve(PROJECT_PATH, './src/components'),
      'Utils': resolve(PROJECT_PATH, './src/utils'),}},module: {/ /... },
  plugins: [/ /... ] .
}

Copy the code

Now, the two can be developed and packaged as normal! Some friends may be confused, I only configure the alias in the Webpack is not ok? Although there will be a red flag during development, it does not affect the correct code, after all, webpack will do path mapping replacement at packaging or development time. Yes, that’s true, but setting it in tsconfig.json will give us an intelligent hint. For example, if I type Com, the editor will give us the correct Components, and the file underneath it will continue to give us the hint.

If you’ve ever worked on a big, document-heavy project, you know that smart tips really smell good.

More Babel configuration

We’ve used Babel to parse react and typescript syntax in the past, but that’s all we’ve done so far. The ES6+ syntax you use in your code remains intact. However, not all browsers support the ES6+ syntax. This is where @babel/preset-env comes in to do the hard work, finding the needed plugin to translate ES6+ syntax based on the target browser environment (Browserslist) set. For example, const or let translates to var.

However, there is no way to translate new ES features such as Promise or.includes into ES5 unless we inject the implementation of the new language features into the packaged file. We use @babel/ plugin-transform-Runtime, which, like @babel/preset-env, provides shims for ES new APIS and can be loaded on demand, but the former doesn’t pollute the prototype chain.

In addition, when compiling each module, Babel inserts helper functions such as _extend as needed. This helper function is generated for each required module, which obviously adds redundancy to the code. The @babel/plugin-transform-runtime plugin imports all helper functions from @babel/runtime-corejs3 (we’ll use corejs3 below) to reduce redundancy.

Install them:

npm install @babel/preset-env @babel/plugin-transform-runtime -D
npm install @babel/runtime-corejs3 -S
Copy the code

Note: the installation of @babel/ Runtime-corejs3 is a production dependency.

Modify.babelre as follows:

{
  "presets": [["@babel/preset-env",
      {
        // Prevent Babel from translating any module type into CommonJS, causing tree-shaking failure
        "modules": false}]."@babel/preset-react"."@babel/preset-typescript"]."plungins": [["@babel/plugin-transform-runtime",
      {
        "corejs": {
          "version": 3."proposals": true
        },
        "useESModules": true}}]]Copy the code

Ok, done!

At this point, the React +typescript development environment is ready for development, but there is still a lot we can do to optimize the development environment and production environment. Keep up the good work!

Common environment optimization

This section focuses on common configuration optimizations that are required in both development and production environments.

1. Copy public static resources

Have you noticed that up to now, there is no icon in our development page, just the following:

Like create-react-app, we put the.ico file in the public/ directory. For example, I copied a CRA favicon.ico file and added the following tags to our index.html file:

<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" />+ 
      <meta name="viewport" content="width=device-width, Initial-scale =1.0" /> <title>React+Typescript quick development scaffolding </title> </head> <body> <div id="root"></div> </body> </ HTML >Copy the code

Ico file is not in the dist directory, so the import of the HTML file will definitely not work. We wanted a way to copy static resources from the public/ folder to the dist folder we generated after packaging. Unless you want to manually copy after each packaging, use the copy-webpack-plugin.

Install it:

npm install copy-webpack-plugin -D
Copy the code

Modify the webpack.common.js file and add the following code:

const CopyPlugin = require('copy-webpack-plugin')

module.exports = {
	plugins: [
    / / other plugin...
  	new CopyPlugin({
      patterns: [{context: resolve(PROJECT_PATH, './public'),
          from: The '*'.to: resolve(PROJECT_PATH, './dist'),
          toType: 'dir',},],}),]}Copy the code

Then you can restart the NPM run start and clear the page cache again. You will see our little icon come up. Now you can replace it with your own icon.

Similarly, any other static resource files that you throw into the public/ directory will be automatically copied to the dist/ directory.

Note ⚠️ : the html-webpack-plugin plugin is used to configure the cache: False, if not, you will refresh the page after code modification, the HTML file will not introduce any packaged JS file, naturally no JS code is executed, particularly terrible, I have been doing for a long time, I checked the official issue of copy-webpack-plugin to find the solution.

2. The compilation progress is displayed

After we run NPM run start or NPM run build, there is no information on the console to tell us how far we are in compiling. In large projects, the speed of compiling and packaging often takes a long time. If you are not familiar with the project’s urinals, you will think it is stuck. Which greatly increased anxiety… Therefore, it is important to show the progress of packaging, which is a positive feedback to the developer.

In my opinion, hope is really important in one’s heart when one is alive.

We can do this with the help of webPackBar, which is installed:

npm install webpackbar -D
Copy the code

Add the following code to webpack.common.js:

const WebpackBar = require('webpackbar')

module.exports = {
	plugins: [
    / / other plugin...
  	new WebpackBar({
      name: isDev ? 'Starting' : 'Packing'.color: '#fa8c16',]}})Copy the code

Now we have the progress of local service or packaging, isn’t it particularly comfortable? I really like this plugin.

3. Typescirpt type checking at compile time

Configuration before we said Babel, in order to compile speed, Babel compilation of ts directly remove type, does not do to the type of ts inspection, look at an example, we see before I create the SRC/app. The TSX composite file, I don’t deliberately deconstructed a prior statement type:

As shown above, the wrong I was trying to deconstruct was not declared in our IProps, and there would be errors in the editor, but the point is that at some point, a person or a situation made such a mistake, and it didn’t deal with the problem. After we took over the project, we didn’t know there was such a problem. It can then be developed or packaged locally, completely losing the benefits of typescript type declarations and introducing significant hidden bugs!

So we need the fork-ts-checker-webpack-plugin to give us an error when we pack or start the local service, so let’s install it:

npm install fork-ts-checker-webpack-plugin -D
Copy the code

Add the following code to webpack.common.js:

const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')

module.exports = {
	plugins: [
    / / other plugin...
  	new ForkTsCheckerWebpackPlugin({
      typescript: {
        configFile: resolve(PROJECT_PATH, './tsconfig.json'),},}),]}Copy the code

Now, if we run NPM run build, we will get the following error:

Instead of being unaware of the hidden bug, we can fix it once we find it.

4. Speed up secondary compilation

By “second” I mean every build after the first build.

Node_modules /. Cache /hard-source plugin is the hard-source-webpack-plugin that provides an intermediate cache for modules such as Lodash. The first build takes slightly more time because it does a caching job, but every build after that gets a lot faster! Let’s install it first:

npm install hard-source-webpack-plugin -D
Copy the code

Add the following code to webpack.common.js:

const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')

module.exports = {
	plugins: [
    / / other plugin...
  	new HardSourceWebpackPlugin(),
  ]
}
Copy the code

At this point, we run NPM run start or NPM run build twice to see the comparison of the time spent:

As the project gets bigger, this speed gap becomes more pronounced.

5. External reduces packaging volume

Up to now, both development and production, we have to first use Webpack to insert react and react-dom code into our final generated code. Just imagine, when such third-party packages become more and more, the final printed files will be very large. Users need to download such a large file every time they enter the page, which will lead to a longer white screen time, which will seriously affect user experience. Therefore, we will strip out such third-party packages or adopt the form of CDN link.

Modify webpack.common.js to add the following code:

module.exports = {
	plugins: [
    / / other plugin...].externals: {
    react: 'React'.'react-dom': 'ReactDOM',}}Copy the code

In development, we use react and react-dom like this:

import React from 'react'
import ReactDOM from 'react-dom'
Copy the code

So, when we finally get to the end of the package that doesn’t inject the code for these two packages, there must be another way to import them, otherwise the program will not run properly, so we open public/index.html and add the following CDN link:

<! DOCTYPE html> <html lang="en"> <body> <div id="root"></div>+ < script crossorigin SRC = "https://unpkg.com/[email protected]/umd/react.production.min.js" > < / script >
+ < script crossorigin SRC = "https://unpkg.com/[email protected]/umd/react-dom.production.min.js" > < / script >
  </body>
</html>
Copy the code

Their respective versions can be determined in package.json!

Then we compare the packaging volume before and after adding externals and see a big difference.

If I add externals to the file, the size of the file will not change. If I add externals to the file, the size of the file will not change. In fact, it has the following advantages:

  • HTTP cache: Users do not need to download react and react-dom after the first download, according to the cache policy of the browser.
  • Webpack build time reduced: It’s faster because there’s one less step to package react and React-DOM.

If you want to configure externals for your own project, you can do this. However, if you use this scaffolding to develop the React component and need to publish it to NPM, then if you do not put the react dependency into the final output package, you can configure externals for your own project. NPM install [email protected] -s if you download the react package. You can’t guarantee that the react version is the same as yours. We’ll talk about this later

6. Remove common code

Let’s start with lazy loading in ES6.

Lazy loading is a powerful tool to optimize the speed of the first screen of a web page. Here is a simple example to show you what the benefits are.

In general, we introduce a utility function like this:

import { add } from './math.js';
Copy the code

If introduced this way, the code in the math.js file will go into the final package after the package is packaged, even though the **add ** method may not be used on the first screen! ** The downside is obvious, I don’t even need to use it on the first screen, but suffer the consequences of slow response times when downloading the current redundant code!

However, if we now introduce in the following way:

import("./math").then(math= > {
  console.log(math.add(16.26))})Copy the code

Webpack automatically parses the syntax, splits the code, and when packaged, the code in Math.js is automatically split into a separate chunk file, which the page will download and execute only if we call the method during page interaction.

Similarly, lazy loading of the React component can be done with the help of react. lazy and react. Suspense.

SRC/app. TSX:

import React, { Suspense, useState } from 'react'

const ComputedOne = React.lazy(() = > import('Components/ComputedOne'))
const ComputedTwo = React.lazy(() = > import('Components/ComputedTwo'))

function App() {
  const [showTwo, setShowTwo] = useState<boolean>(false)

  return (
    <div className='app'>
      <Suspense fallback={<div>Loading...</div>} ><ComputedOne a={1} b={2} />
        {showTwo && <ComputedTwo a={3} b={4} />}
        <button type='button' onClick={()= >Display Two setShowTwo (true)} ></button>
      </Suspense>
    </div>)}export default App
Copy the code

SRC/components/ComputedOne/index. TSX:

import React from 'react'
import './index.scss'
import { add } from 'Utils/math'

interface IProps {
  a: number
  b: number
}

function ComputedOne(props: IProps) {
  const { a, b } = props
  const sum = add(a, b)

  return <p className='computed-one'>{`Hi, I'm computed one, my sum is ${sum}.`}</p>
}

export default ComputedOne
Copy the code

The ComputedTwo component code is similar to the ComputedOne component code in that Math.ts is a simple summation function without pasting the code.

Next, we NPM run start, and open the console Network, will find the following dynamic load chunk files:

The above demonstration implements lazy loading of components. Next, run the NPM run build to see what comes out of the package:

The files boxed in red are the code for two components (ComputedOne and ComputedTwo), and the benefits are obvious:

  • If an imported component is loaded lazily, the package name is not changed if the component code is unchanged. After the component is deployed in the production environment, users do not need to download the file again because of browser cache, shortening the web interaction time.
  • Prevent all components into a package, reducing the first screen time.

The advantages brought by lazy loading should not be underestimated. We extended our thinking along this mode, if we could classify some referenced third-party packages into separate chunks, would they also have the same advantages?

The answer is yes, because as long as the version of the third-party dependency package is locked, the code will not change, so every iteration of the project code will not affect the name of the chunk file of the dependency package, so it will have the same advantages!

This function is enabled by default in Webpack4, which is why the lazy loading shown above will render independent chunks. However, to render independent chunks for third-party dependencies, we need to add the following code to webpack.common.js:

module.exports = {
	// other...
  externals: {/ /... },
  optimization: {
    splitChunks: {
      chunks: 'all'.name: true,,}}}Copy the code

NPM run build = NPM run build

This chunk contains some code for third-party packages that we didn’t cull through externals. If you don’t want to introduce React and react-DOM in CDN form, you can also configure them separately. On the other hand, if you have a multi-page application, you also need to configure the public module to be removed. Since we are building a single-page application development environment, we will not demonstrate this.

I recommend two places to learn splitChunks configuration: 1. 2. Understand WebPack4. SplitChunks.

Development (DEV) environment optimization

This section focuses on common configuration optimizations required by both the development environment and the development environment.

1. Hot update

If you’ve ever had to endure the pain of making minor changes to your code and having your page refresh, hot updates are something to learn! May be small projects you feel nothing, are the same fast, but the project is big every compilation is straight to the heart of pain!

A hot update is a “partial refresh” of the page where you have changed it. This may not be an exact term, but you get the idea. Open webpack.dev.js and perform the following three steps to use it:

Step 1: Set the hot property under devServer to true.

Step 2: add webpack. HotModuleReplacementPlugin plugin:

const webpack = require('webpack')

module.exports = merge(common, {
  devServer: {/ /... },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
  ]
})

Copy the code

At this point, you NPM run start and try to change part of the code, save it and find that the whole page is still refreshed. If you want to get the above “part refresh”, you need to add the code in the project entry file.

Step 3: Modify the entry file. For example, I choose SRC /index.js as my entry file:

import React from 'react'
import ReactDOM from 'react-dom'
import App from './app'

if (module && module.hot) {
  module.hot.accept()
}

ReactDOM.render(<App />.document.querySelector('#root'))
Copy the code

An error is reported because of ts:

We just need to install @types/webpack-env:

npm install @types/webpack-env -D
Copy the code

Now, let’s restart the NPM run start and make random changes to the page to see if it doesn’t refresh as a whole. Comfortable ~

2. Cross-domain request

In general, the interface proxy can be configured to make cross-domain requests using devServer’s native Proxy field, but in order to keep the code building the environment separate from the business code, we need to separate the configuration file. This can be done:

Step 1: Create a new setproxy.js file under SRC/and write the following code:

const proxySettings = {
  // Interface proxy 1
  '/api/': {
    target: 'http://198.168.111.111:3001'.changeOrigin: true,},// Interface proxy 2
  '/api-2/': {
    target: 'http://198.168.111.111:3002'.changeOrigin: true.pathRewrite: {
      '^/api-2': ' ',}},/ /...
}

module.exports = proxySettings
Copy the code

Once configured, we need to introduce and properly enlarge the devServer proxy field in webpack.dev.js.

Step 2: Simple introduction and deconstruction:

const proxySetting = require('.. /.. /src/setProxy.js')

module.exports = merge(common, {
  devServer: {
    / /...
    proxy: { ...proxySetting }
  },
})

Copy the code

Ok! It’s that simple! Next, install axios, our most common request sending library:

npm install axios -S
Copy the code

A simple request from SRC /app.tsx allows you to test on your own. If you’re looking for a test interface, you can check out github’s public API

Production (PROD) environment optimization

This section focuses on common configuration optimizations that are required in both development and production environments.

1. Pull out the CSS styles

The advantages of pulling out separate chunks of files were briefly described in the section “Pulling out common code” above. Now all the styles we write are packed into JS files. If left unchecked, the files will get bigger and bigger.

To split CSS styles with the mini-CSs-extract-plugin, install it first:

npm install mini-css-extract-plugin -D
Copy the code

Add and modify the following code in the webpack.common.js file (note ⚠️, the common file) :

const MiniCssExtractPlugin = require('mini-css-extract-plugin')

const getCssLoaders = (importLoaders) = > [
  isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
  / /...
]

module.exports = {
	plugins: [
    / / other plugin...! isDev &&new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash:8].css'.chunkFilename: 'css/[name].[contenthash:8].css'.ignoreOrder: false,]}})Copy the code

We have modified the getCssLoaders this method, the original no matter in what environment we use are all style – loader, because we don’t need to pull away in the development environment, then made a judgment, in a production environment using MiniCssExtractPlugin. Loader.

Let’s do some random styling and do the followingnpm run buildAnd to thedistTake a look in the directory:

You can see that the style chunk file has been successfully unpacked and enjoyed the supreme treatment!

2. Remove useless styles

I deliberately styled a class name in the style file that is not used:

As a result, I performed the packaging and found the detached style file and clicked it to see:

Purgecss-webpack-plugin: node-glob: node-glob: node-glob: node-glob: Node-glob: node-glob: node-glob: node-glob: node-glob: node-glob: node-glob: node-glob: node-glob: node-glob:

npm install purgecss-webpack-plugin glob -D
Copy the code

Then add the following code to webpack.prod.js:

const { resolve } = require('path')
const glob = require('glob')
const PurgeCSSPlugin = require('purgecss-webpack-plugin')
const { PROJECT_PATH } = require('.. /constants')

module.exports = merge(common, {
	// ...
  plugins: [
    new PurgeCSSPlugin({
      paths: glob.sync(`${resolve(PROJECT_PATH, './src')}/**/*.{tsx,scss,less,css}`, { nodir: true }),
      whitelist: ['html'.'body']}),],})Copy the code

Simple explanation under the configuration above: glob is used to find the path to the file, we found the SRC synchronous the suffix for below. The TSX,. | | le c (sc) ss file path and return to the paths in the form of an array, then the plug-in will parse each corresponding file path, remove useless style; Nodir is the path to remove folders to speed up processing. To give you an intuition of the path array, it will print like this:

[
  '/Users/RMBP/Desktop/react-ts-quick-starter/src/app.scss',
  '/Users/RMBP/Desktop/react-ts-quick-starter/src/app.tsx',
  '/Users/RMBP/Desktop/react-ts-quick-starter/src/components/ComputedOne/index.scss',
  '/Users/RMBP/Desktop/react-ts-quick-starter/src/components/ComputedOne/index.tsx',
  '/Users/RMBP/Desktop/react-ts-quick-starter/src/components/ComputedTwo/index.scss',
  '/Users/RMBP/Desktop/react-ts-quick-starter/src/components/ComputedTwo/index.tsx',
  '/Users/RMBP/Desktop/react-ts-quick-starter/src/index.tsx'
]
Copy the code

We should note ⚠️ : must also be introduced to the TSX file path to the style, otherwise you can not resolve which style class name, naturally also can not correctly remove useless styles.

Now look at the style file we packaged, without that extra code, it’s nice!

3. Compress JS and CSS code

In a production environment, compressing code is a must, and the size of the package can be reduced by more than half.

Js code compression

Webpack4 js code compression artifact terser-webpack-plugin can be described as everyone knows it? It supports compression of ES6 syntax and is enabled by default when mode is production. Yes, Webpack4 is fully built-in, but we need to install it first in order to do some additional configuration:

npm install terser-webpack-plugin -D
Copy the code

Optimization in the webpack.common.js file adds the following configuration:

module.exports = {
	// other...
  externals: {/ /... },
  optimization: {
    minimize: !isDev,
    minimizer: [
      !isDev && new TerserPlugin({
        extractComments: false.terserOptions: {
          compress: { pure_funcs: ['console.log'] },
        }
      })
    ].filter(Boolean),
    splitChunks: {/ /... },}},Copy the code

First add minimize, which specifies the compressor. If we set it to true, we default to terser-webpack-plugin. If we set it to false, we don’t compress the code. And then, in minimize, if you’re in a production environment, you turn on compression.

  • extractCommentsSet tofalseIt means to remove all comments except those with special markup, such as@preserveTag, and later we’ll use another plug-in to generate our custom annotations.
  • pure_funcsWe can set the functions that we want to remove, like I’m going to put all the functions in the codeconsole.logGet rid of.

CSS code compression

Optimize – CSS – Assets -webpack-plugin

npm install optimize-css-assets-webpack-plugin -D
Copy the code

Add a new code to the minimizer we configured above:

module.exports = {
  optimization: {
    minimizer: [
      // terser! isDev &&new OptimizeCssAssetsPlugin()
    ].filter(Boolean),}}Copy the code

4. Add package comments

We configured Terser to remove all comments from the code when packaging, except for special tags such as @preserve. We want people to see our own declarations when they use our packages (like react), so they can use the BannerPlugin built into WebPack, no installation required!

Add the following code to the webpack.prod.js file and write the desired declaration comment:

const webpack = require('webpack')

module.exports = merge(common, {
  plugins: [
    // ...
    new webpack.BannerPlugin({
      raw: true.banner: '/** @preserve Powered by react-ts-quick-starter (https://github.com/vortesnail/react-ts-quick-starter) */',})]})Copy the code

At this point, open a package and go to the dist directory to see the export files:

5. tree-shaking

Tree-shaking is a packaged code optimization wizard built into WebPack. In production, when mode is set to Production, packaging removes unused code brought in through the ES6 syntax import. Here’s a quick example:

Write the following code in SRC /utils/math.ts:

export function add(a: number, b: number) {
  console.info('I am add func')
  return a + b
}

export function minus(a: number, b: number) {
  console.info('I am minus func')
  return a - b
}
Copy the code

Go back to our SRC /app.tsx, clear the previous content and write the following code:

import React from 'react'
import { add, minus } from 'Utils/math'

function App() {
  return <div className='app'>{add(5, 6)}</div>
}

export default App
Copy the code

As you can see, we introduced both add and minus methods, but in practice we only use add. When we build, open the packaged file and search console.info(‘I am minus func’), which is not available. But finding console.info(‘I am add func’) means that the method is being removed because it is not being used. That’s what tree-shaking is for!

In my projects, I don’t configure sideEffects: false in package.json because I write modules that I can guarantee have no sideEffects.

It’s worth recalling here that in. Babelrc we configured module under @babel/preset-env: False, the purpose is not to process import and export keywords as commonJS module import, such as require, in order to support tree-shaking, as we said above, it is valid for ES6 module import.

6. Package analysis

Sometimes we want to know what packages are being produced, and how big they are, by simply using webpack-bundle-Analyzer, which we install:

npm install webpack-bundle-analyzer -D
Copy the code

Open webpack.prod.js and add the following plugin:

const webpack = require('webpack')

module.exports = merge(common, {
  plugins: [
    // ...
    new BundleAnalyzerPlugin({
      analyzerMode: 'server'.// Open a local service to view the report
      analyzerHost: '127.0.0.1'./ / host Settings
      analyzerPort: 8888.// Set the port number})],})Copy the code

After the NPM run build is complete, the default browser will open and the bundle analysis page will appear:

Feel free to use it! ~

The first half of the conclusion

You read to this point, or follow to this point, I believe you feel that the trip must be worthwhile, right? React-router-dom, React-redux, Mobx and other libraries that are often used in the project can be installed and used according to normal development.

In the second half, I would like to illustrate the entire process of developing the React component and general tools using our existing framework and publishing them to NPM with two examples:

  • The entire process of packaging the toolkit and publishing it to NPM with ROLLup and TSC
  • Use ROLLup and TSC to package the react component and publish it to the NPM process

This part can also talk about a lot of things, I will write another article to explain, please look forward to it! I spent about a month writing this article in my spare time. I hope you can give me a little encouragement. I just need to give my github/blog a little star✨ to make me full of vitality! Ball ball 🙏!!