Webpack 4.0+, React 16.0.0+, Babel 7+


Author: Zhao Weilong

Write at the beginning: When writing the first article of the team with excitement and apprehension, on the one hand, the excitement came from the fact that it was the first time for us to open a window to the outside world since the establishment of the team; on the other hand, we would continue to listen to opinions and maintain a communicative mentality.

React has been updated with 16.0.0 in master branch 2017.09.27.

But aside from the change we’ve all been waiting for, there are also many things worth mentioning.

In conjunction with Webpack 4.0,Babel 7 is where we implement a front-end scaffolding that basically meets everyday development needs

(Bright spot!! We implemented our own react-loadable and react-redux functionality with new features)


Let’s start with the build file and see what Babel 7 and Webpack 4 have done for me in compiling and building.

Previous versions of babelrc are dependent on babel-preset- ES20 ** including stage-* configurations, which the authors find too tedious in the new release and are preferred to the latest release (see their research and reasons). So this is what happens to our babelrc

  {
    "presets": [["@babel/preset-env", {"modules": false.// Still tree-shaking compatible for Webpack}]."@babel/preset-react"."@babel/preset-stage-0",]."plugins": [
      "@babel/plugin-syntax-dynamic-import"],}Copy the code

React is still a stage-0 specification that needs to be configured separately. The new native API supports syntax-dynamic-import. Another issue you may have noticed is that all Babel 7 Packages are written this way (@babel/x) for the reason thatblogThere are also.

A few more changes for Webpack 4

The biggest change is likely to be that Parcel has a 0 configuration, putting more pressure on WebPack, which is already a bit cumbersome to configure. Use mode provides cli mode, of course you can also declare in the configuration file, we will point out later

Webpack — Mode production webpack –mode development What configuration options are included in this default mode

  1. Browser debugging tool (devtool by default)
  2. Faster compile environment cycles (cache setup)
  3. The production environment contains 1. File output size compression (UGLIY processing) 2. Faster packaging time 3. Set the global environment variable production 4. Do not expose the source code and file path 5. Easy to use output resources (there are a lot of things like hosting internal code optimized default use)

(Both modes even allow you to set entry and output paths by default, but we’ll leave that to ourselves for readability and configurability.)

Another important change was that the CommonsChunkPlugin was officially deprecated for the following reasons: 1. According to the official opinion, first of all, this API is not easy to understand and not easy to use 2. Moreover, the extracted public file contains a lot of redundant code 3. This file must be loaded first every time an asynchronous load is performed.

Instead, code-splitting is now supported by default (as long as you use a dynamically loaded API => import()) Webpack will do code splitting and loading asynchronously by default, and it is not restricted by the mode mode mentioned above.

It is written as follows:

const Contract = asyncRoute((a)= > import('./pages/contract'), {
  loading: Loading,
})
Copy the code

This might seem a bit odd, but the normal way to write it is simply to import and return a promise


import(/* webpackChunkName: "lodash" */ 'lodash').then(_= > {
    var element = document.createElement('div')
    element.innerHTML = _.join(['Hello'.'webpack'].' ')
    return element
}).catch(error= > 'An error occurred while loading the component')

Copy the code

However, we return a React component, so we need to do some processing. In addition, you may need a friendly loading interface for asynchronous loading since it is a network request. (The specific granularity of asynchronous loading needs to be determined by yourself Tainer then loads the corresponding component in the page.)

Here we encapsulate the asyncRoute itself. In addition to returning us a normal component, we can also pass it a loading to handle the error information captured during the loading interface and request. If we want to support SSR, we also need to give a special flag to do so Different processing, so without further ado how does the code implement this asyncRoute

// Here's how it works
// e.x author: zhaoweilong
// const someRouteContainer = asyncRoute(() => import('.. /componet'), {
// loading: 
      
       loading... 
      
// })
// <Route exact path='/router' componet={someRouteContainer} />

// function Loading(props) {
// if (props.error) {
// return 
      
Error!
;
// } else { // return
Loading...
;
/ /} // } const asyncRoute = (getComponent, opts) = > { return class AsyncRoute extends React.Component { static Component = null state = { Component: AsyncRoute.Component, error: null, } componentWillMount() { if (!this.state.Component) { getComponent() .then(module= > module.default || module) .then(Component= > { AsyncRoute.Component = Component this.setState({ Component }) }) .catch(error= > { this.setState({ error }) }) } } render() { const { Component, error } = this.state const loading = opts.loading if(loading && ! Component) {return React.createElement(loading, { error, }) } else if (Component) { return <Component {. this.props} / > } return null } } } Copy the code

(this does not include SSR processing, SSR requires you to preload the components.) We haven’t said what our real WebPack configuration would look like:

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

const port = process.env.PORT || 3000

module.exports = {
  target: 'web'.entry: {
    bundle: [
      './src/index.js',]},output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js'.publicPath: '/',},module: {
    rules: [{test: /\.js$/.use: 'babel-loader'.exclude: [/node_modules/],},],},mode: 'development'.devtool: 'cheap-module-source-map'.// We need to replace the default devtool setting eval to be compatible with the react ErrorBoundary
  plugins: [
    new HtmlWebpackPlugin(
      {
        filename: './src/index.html',}),]}Copy the code

It can be seen that we only use HtmlWebpackPlugin to dynamically load the compiled files. Entry and output are also defined by ourselves because of the need for customization and easy maintenance. Configuration files are extremely simple, so you may wonder that the development environment is simple, but how about the production environment?

const webpack = require('webpack')
const devConfig = require('./webpack.config')

const ASSET_PATH = process.env.ASSET_PATH || '/static/'

module.exports = Object.assign(devConfig, {
  entry: {
    bundle: './src/index.js',},output: Object.assign(devConfig.output, {
    filename: '[name].[chunkhash].js'.publicPath: ASSET_PATH,
  }),
  module: {
    rules: [
      ...devConfig.module.rules,
    ]
  },
  mode: 'production'.devtool: 'none',})Copy the code

It’s much simpler, we just need to customize the output as we need, no plugin options at all, and look what the file looks like after we build it:

In addition to the import file of the bundle, we have added the 0,1, and 2 files, which extract the react and index files and a route contract js file loaded asynchronously

With configuration done, let’s take a look at some of the exciting new React features and applications

We’ll focus on four features and play with three

  • Added the ErrorBoundary component catch component error
  • Abandoned componentWillReceiveProps replacement for static getDerivedStateFromProps
  • Add render props
  • The new context API

Let’s start with the first change

React felt that the error notification mechanism was too inhumane, so we were allowed to wrap the component ErrorBoundary around the component. This custom component will have its own life cycle for componentDidCatch.

import React from 'react'
import styled from 'styled-components'

const StyledBoundaryBox = styled.div,0,0,0.4 ` background: rgba (0); position: fixed; top: 0; left: 0; right: 0; bottom: 0; width: 100%; height: 100%; z-index: 2; `
const Title = styled.h2` position: relative; padding: 0 10px; font-size: 17px; color: #0070c9; z-index: 1991; `

const Details = styled.details` position: relative; padding: 0 10px; color: #bb1d1d; z-index: 1991; `

class ErrorBoundary extends React.Component {
  state = {
    hasError: false.error: null.errorInfo: null,
  }

  componentDidCatch(error, info) {
    this.setState({
      hasError: true.error: error,
      errorInfo: info,
    })
  }

  render() {
    if (this.state.hasError) {
      return(
        <StyledBoundaryBox>
          <Title>There may be an error in the page!</Title>
          <Details>
            {this.state.error && this.state.error.toString()}
            <br/>
            {this.state.errorInfo.componentStack}
          </Details>
        </StyledBoundaryBox>)}return this.props.children
  }
}

export default ErrorBoundary
Copy the code

Wrap it around the component you want to catch. I just put it in the outermost layer. Of course, you can follow Dan’s instructions to separate the corresponding sections of the catch page. In fact, you’ll notice that this component is very similar to the try{}catch{} block in JS. In fact, React wants this development experience to be closer to native JS

When there is an error when you will find in the details of an error component call stack, convenient for you to locate the error, of course, you can define the style of error here too ugly please ignore!!

/ / before
class ExampleComponent extends React.Component {
  state = {
    derivedData: computeDerivedState(this.props)
  };

  componentWillReceiveProps(nextProps) {
    if (this.props.someValue ! == nextProps.someValue) {this.setState({
        derivedData: computeDerivedState(nextProps) }); }}}/ / after
class ExampleComponent extends React.Component {
  state = {};

  static getDerivedStateFromProps(nextProps, prevState) {
    if(prevState.someMirroredValue ! == nextProps.someValue) {return {
        derivedData: computeDerivedState(nextProps),
        someMirroredValue: nextProps.someValue
      };
    }
    return null; }}}Copy the code

We find that first of all we don’t need to change this.setState, but the part of return that changes (this is what setState does). If there is no return NULL, other properties will remain unchanged. CWRP () only updates when the component props are updated, but the new GDSFP () will walk when it is first mounted on inital mount, which you might think is a little weird because I used to use this If you want to get props from state, you want to store props in state. If you want to compare the props stored before with the nextProps that might change later, you can just do it Either dispatch(someAction) or return{} works. But the question is if I use Redux do I have to store a copy of the changed data in state instead of all in the global store? This is a very sensitive and big topic (as it relates to the future of React itself and the future of redux, including React-Redux). If you’re interested, you can check out the discussion with Redux author Dan and a few key members. It’s very enlightening The chapter will also discuss its possibilities. If you keep following us!!

Let’s talk about the render props update, which I’m personally excited about because it directly affects our programming experience

(You can check out this concept in detail on the website.)

This concept was already in use in React – Router4 if you remember something like this:

  <Route
    exact
    path='/'
    render={() => <Pstyled>Welcome!</Pstyled>} / >Copy the code

If you’re still using Mixins then there’s a gap between us. When we talked about HOC implementations, we generally thought of higher-order components, but it has some drawbacks (let’s take a look):

(Using an official example)

import React from 'react'
import ReactDOM from 'react-dom'

const withMouse = (Component) = > {
  return class extends React.Component {
    state = { x: 0.y: 0 }

    handleMouseMove = (event) = > {
      this.setState({
        x: event.clientX,
        y: event.clientY
      })
    }

    render() {
      return (
        <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
          <Component {. this.props} mouse={this.state}/>
        </div>
      )
    }
  }
}

const App = React.createClass({
  render() {
    // Instead of maintaining our own state,
    // we get the mouse position as a prop!
    const { x, y } = this.props.mouse
    return (
      <div style={{ height: '100'}} % >
        <h1>The mouse position is ({x}, {y})</h1>
      </div>
    )
  }
})

const AppWithMouse = withMouse(App)

ReactDOM.render(<AppWithMouse/>, document.getElementById('app'))

Copy the code
  • The first problem is that you don’t know exactly what is being delivered to you in hoc that is changing your props if it is also third-party. That’s more of a black box problem.
  • Problem two, naming conflicts, because you always have a function name called withMouse

What if render props solved these two problems?

import React from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'

// We can implement hoc with normal Component
class Mouse extends React.Component {
  static propTypes = {
    render: PropTypes.func.isRequired
  }

  state = { x: 0.y: 0 }

  handleMouseMove = (event) = > {
    this.setState({
      x: event.clientX,
      y: event.clientY
    })
  }

  render() {
    return (
      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
      {this.props.render(this.state)}
      </div>)}}const App = React.createClass({
  render() {
    return (
      <div style={{ height: '100'}} % >
        <Mouse render={({ x.y}) = >(// The transfer is clear<h1>The mouse position is ({x}, {y})</h1>)} / ></div>
    )
  }
})

ReactDOM.render(<App/>, document.getElementById('app'))
Copy the code

Do not feel that no matter from the transfer value to the final use are so simple as before!! (This.props. Children can also be used as a function.)

How do you implement react-redux with it? First, we know that connect()() is a typical HOC

Here is our implementation:


import PropTypes from 'prop-types'
import React, { Component } from 'react'

const dummyState = {}

class ConnectConsumer extends Component {
  static propTypes = {
    context: PropTypes.shape({
      dispatch: PropTypes.func.isRequired,
      getState: PropTypes.func.isRequired,
      subscribe: PropTypes.func.isRequired,
    }),
    children: PropTypes.func.isRequired,
  }

  componentDidMount() {
    const { context } = this.props
    this.unsubscribe = context.subscribe((a)= > {
      this.setState(dummyState)
    })
  }

  componentWillUnmount() {
    this.unsubscribe()
  }

  render() {
    const { context } = this.props
    const passProps = this.props
    return this.props.children(context.getState(), context.dispatch)
  }
}

Copy the code

Isn’t that cool? How does he use it? We passed state dispatch so it’s going to be used in a similar way to the way we passed it and maybe a little bit more intuitive.

const ConnectContract = (a)= > (
  <Connect>{(state, dispatch, passProps) => {// This is a function,Do ever you want const {addStars: { num } } = state const props = { num, onAddStar: (... args) => dispatch(addStar(... args)), onReduceStart: (... args) => dispatch(reduceStar(... args)), } return (<Contract {. props} / >)}}</Connect>
)
Copy the code

You may question, wait… What about our ? React 16.3.0 Uses the new Context API under water

import React, { createContext, Children } from 'react'

export const StoreContext = createContext({
  store: {},})export const ProviderComponent = ({ children, store }) = > (
  <StoreContext.Provider value={store}>
    {Children.only(children)}
  </StoreContext.Provider>
)
Copy the code
import { StoreContext } from './provider'


const Connect = ({ children }) = > (
  <StoreContext.Consumer>
    {(context) => (
      <ConnectConsumer context={context}>
        {children}
      </ConnectConsumer>
    )}
  </StoreContext.Consumer>
)

Copy the code

Well, that’s the new API. You might find that calling the createContext method generates two properties of the object, a React Component and one called provider One is called consumer, and if you’re wondering why you changed it, you have to mention that the previous context had some problems, and the reasons are all in here

I’m not going to say much here, but the main reason I’m going to say that the old method of passing is shouldComponentUpdate blocks Context Changes will be blocked and updated by this lifecycle, but the new method won’t be because you’ll be able to consumer when you need it and pass it as parameters to the child components you actually need to use using the render props we talked about earlier. Does it feel like he doesn’t even have the big picture anymore?

With so many cool things introduced, it looks like our new architecture is coming out, heh heh!

If you want to try it, please visit here and like it!!

As a final conclusion

We are the front-end team of Didi AMC Business Division. There will be more interesting sharing in the future. Welcome to follow the column! By the way, the next episode will be about Redux! (Any questions please leave a message to communicate!)