搭建React全家桶框架

搭建后的全家桶框架目录名字

1. 创建文件夹react-family并进入

2. npm init -y 生成package.json

3.安装webpack

3.1 npm install --save-dev webpack

3.2 创建webpack.dev.config.js

const path = require('path')

module.exports = {  

    /* 入口 */  

    entry: path.join(__dirname, 'src/index.js'),  

    /* 输出到dist文件夹,输出文件名字为bundle.js */  

    output: {    

        path: path.join(__dirname, './dist'),

        filename: 'bundle.js'  

    }

}


3.3 新建入口文件

创建src文件夹,并在src下添加index.js文件

document.getElementById(‘app’).innerHTML = “Webpack!”

3.4 执行命令 webpack --config webpack.dev.config.js

生成了dist文件夹和bundle.js,在dist文件添加index.html文件,并引入bundle.js,在浏览器打开index.html,可以看到Webpack!


index.html

4 Babel

Babel 把用最新标准编写的 JavaScript 代码向下编译成可以在今天随处可用的版本。 这一过程叫做“源码到源码”编译, 也被称为转换编译。

babel-core 调用Babel的API进行转码

babel-loader

babel-preset-es2015 用于解析 ES6

babel-preset-react 用于解析 JSX

babel-preset-stage-0 用于解析 ES7 提案

npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-0

其中babel-loader 8.1.0版本和 babel-core 6.26.3版本不兼容,可以babel-loader改为7版本

新建babel配置文件.babelrc

{

  "presets": [

    "es2015",

    "react",

    "stage-0"

  ],

  "plugins": []

}

修改webpack.dev.config.js,增加babel-loader!

module: {

    rules: [{

        test: /\.js$/,

        use: ['babel-loader?cacheDirectory=true'],

        include: path.join(__dirname, 'src')

    }]

  }

测试下,修改入口文件 src/index.js

var func = str => {

  document.getElementById('app').innerHTML = str

}

func('我现在在使用Babel!')

执行打包命令webpack --config webpack.dev.config.js

浏览器打开index.html,我们看到正确输出了我现在在使用Babel!

打开打包后的bundle.js,可以看到箭头函数被转换成普通函数了!

5 react

npm install --save react react-dom

添加Hello组件  src/components/Hello/Hello.js

import React, { Component } from 'react'

export default class Hello extends Component {

  render () {

    return (

      <div>

        hello react

      </div>

    )

  }

}

修改入口文件 src/index.js使用react

import React from 'react';

import ReactDom from 'react-dom';

import Hello from './components/Hello/Hello';

ReactDom.render(

    <Hello/>, document.getElementById('app'));

打包webpack --config webpack.dev.config.js,打开index.html查看效果

6 命令优化

每次打包都得执行长命令webpack –config webpack.dev.config.js,所以我们优化下

修改package.json里面的script,增加dev-build

"scripts": {

    "test": "echo \"Error: no test specified\" && exit 1",

    "dev-build": "webpack --config webpack.dev.config.js"

  },

现在我们打包只需要执行npm run dev-build

7 react-router

cnpm install --save react-router-dom

新建两个页面

// src/pages/Home/Home.js

import React, { Component } from 'react'

export default class Home extends Component {

  render () {

    return (

      <div>

        this is home

      </div>

    )

  }

}

// src/pages/ Page1/Page1.js 

import React, { Component } from 'react'

export default class  Page1 extends Component {

  render () {

    return (

      <div>

        this is  page1

      </div>

    )

  }

}

//  src/router/router.js

import React from 'react'

import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom'

import Home from '../pages/Home/Home'

import Page1 from '../pages/Page1/Page1'

const getRouter = () => (

  <Router>

    <div>

      <ul>

        <li><Link to='/'>首页</Link></li>

        <li><Link to='/page1'>Page1</Link></li>

      </ul>

      <Switch>

        <Route exact path='/' component={Home} />

        <Route path='/page1' component={Page1} />

      </Switch>

    </div>

  </Router>

)

export default getRouter

修改入口文件src/index.js,引用Router

import ReactDom from 'react-dom'

import getRouter from './router/router'

ReactDom.render(

  getRouter(), document.getElementById('app'))

8 webpack-dev-server

cnpm install webpack-dev-server@2 --save-dev

(版本2不会报错)

修改webpack.dev.config.js,增加webpack-dev-server的配置。

devServer: {

    contentBase: path.join(__dirname, './dist'),

    historyApiFallback: true,

    host: '0.0.0.0',

    port: 8765

  }

现在执行 webpack-dev-server --config webpack.dev.config.js

浏览器打开http://localhost:8765,可以页面路由切换了

修改package.json里面的script,增加start

"start": "webpack-dev-server --config webpack.dev.config.js",

color(CLI only) console中打印彩色日志

historyApiFallback 任意的404响应都被替代为index.html。有什么用呢?你现在运行npm start,然后打开浏览器,访问http://localhost:8080,然后点击Page1到链接http://localhost:8080/page1

然后刷新页面试试。是不是发现刷新后404了。为什么?dist文件夹里面并没有page1.html,当然会404了,所以我们需要配置

historyApiFallback,让所有的404定位到index.html。

host 指定一个host,默认是localhost。如果你希望服务器外部可以访问,指定如下:host: “0.0.0.0”。比如你用手机通过IP访问。

hot 启用Webpack的模块热替换特性。

port 配置要监听的端口。默认就是我们现在使用的8080端口。

proxy 代理。比如在 localhost:3000 上有后端服务的话,你可以这样启用代理:

proxy: {

    '/api': '地址'

}

progress(CLI only) 将编译进度输出到控制台。

9 模块热替换(Hot Module Replacement)

当我们修改代码的时候,浏览器会自动刷新,为了修改代码的时候,浏览器不会刷新。

修改package.json 增加 –hot

"start": "webpack-dev-server --config webpack.dev.config.js --color --progress --hot",

src/index.js 增加module.hot.accept(), 当模块更新的时候,通知index.js。

import ReactDom from 'react-dom'

import getRouter from './router/router'

if (module.hot) {

  module.hot.accept()

}

ReactDom.render(

  getRouter(), document.getElementById('app'))

执行npm run start,修改Home.js, 在不刷新页面的情况下,内容更新了

但是当模块热替换的时候,state会重置

// src/pages/Home/Home.js
import React, { Component } from 'react'

export default class Home extends Component {

  constructor (props) {

    super(props)

    this.state = {

      count: 0

    }

  }

  _handleClick () {

    this.setState({

      count: ++this.state.count

    })

  }

  render () {

    return (

      <div>

        this is home~<br />

        当前计数:{this.state.count}<br />

        <button onClick={() => this._handleClick()}>自增</button>

      </div>

    )

  }

}

当我们修改代码的时候,webpack在更新时候,也把count初始为0了

为了在react模块更新的同时,能保留state等页面中其他状态,我们需要引入react-hot-loader

cnpm install react-hot-loader --save-dev

修改.babelrc 增加 react-hot-loader/babel

{

  "presets": [

    "es2015",

    "react",

    "stage-0"

  ],

  "plugins": [

    "react-hot-loader/babel"

  ]

}

webpack.dev.config.js入口增加react-hot-loader/patch

entry: [

    'react-hot-loader/patch',

    path.join(__dirname, 'src/index.js')

  ]

修改src/index.js

import React from 'react'

import ReactDom from 'react-dom'

import { AppContainer } from 'react-hot-loader'

import getRouter from './router/router'

/* 初始化 */

renderWithHotReload(getRouter())

/* 热更新 */

if (module.hot) {

  module.hot.accept('./router/router', () => {

    const getRouter = require('./router/router').default

    renderWithHotReload(getRouter())

  })

}

function renderWithHotReload (RootElement) {

  ReactDom.render(

    <AppContainer>

      {RootElement}

    </AppContainer>,

    document.getElementById('app')

  )

}

执行npm start,修改页面的时候,state不更新

10 文件路径优化

修改webpack.dev.config.js

resolve: {

    alias: {

      pages: path.join(__dirname, 'src/pages'),

      components: path.join(__dirname, 'src/components'),

      router: path.join(__dirname, 'src/router'),

      actions: path.join(__dirname, 'src/redux/actions'),

      reducers: path.join(__dirname, 'src/redux/reducers')

    }

  }

修改后,引用路径就可以这样子了

import Home from 'pages/Home/Home'

import Page1 from 'pages/Page1/Page1'

11 redux

因为太多了,这边简单列一个例子叭

// src/redux/actions/counter.js

export const INCREMENT = 'counter/INCREMENT'

export const DECREMENT = 'counter/DECREMENT'

export const RESET = 'counter/RESET'

export function increment () {

  return { type: INCREMENT }

}

export function decrement () {

  return { type: DECREMENT }

}

export function reset () {

  return { type: RESET }

}

// src/redux/reducers/counter.js

import { INCREMENT, DECREMENT, RESET } from '../actions/counter'

/*

* 初始化state

 */

const initState = {

  count: 0

}

/*

* reducer

 */

export default function reducer (state = initState, action) {

  switch (action.type) {

    case INCREMENT:

      return {

        count: state.count + 1

      }

    case DECREMENT:

      return {

        count: state.count - 1

      }

    case RESET:

      return { count: 0 }

    default:

      return state

  }

}

// src/redux/reducers.js 

import { combineReducers } from 'redux'

import counter from 'reducers/counter'

export default combineReducers({

  counter

})

// src/redux/store.js 
import { createStore } from 'redux'

import combineReducers from './reducers'

let store = createStore(combineReducers)

if (module.hot) {

  module.hot.accept('./reducers', () => {

    const nextCombineReducers = require('./reducers').default

    store.replaceReducer(nextCombineReducers)

  })

}

export default store

在组件中引用redux,react-redux提供了一个方法connect,connect接收两个参数,mapStateToProps,就是把redux的state,转为组件的Props,mapDispatchToprops, 就是把发射actions的方法,转为Props属性函数。

cnpm install --save react-redux

创建src/pages/Counter/Counter.js,并在路由文件添加路由

import React, { Component } from 'react'

import { increment, decrement, reset } from 'actions/counter'

import { connect } from 'react-redux'

class Counter extends Component {

  render () {

    return (

      <div>

        <div>当前计数为{this.props.counter.count}</div>

        <button onClick={() => this.props.increment()}>自增

        </button>

        <button onClick={() => this.props.decrement()}>自减

        </button>

        <button onClick={() => this.props.reset()}>重置

        </button>

      </div>

    )

  }

}

const mapStateToProps = (state) => {

  return {

    counter: state.counter

  }

}

const mapDispatchToProps = (dispatch) => {

  return {

    increment: () => {

      dispatch(increment())

    },

    decrement: () => {

      dispatch(decrement())

    },

    reset: () => {

      dispatch(reset())

    }

  }

}

export default connect(mapStateToProps, mapDispatchToProps)(Counter)

在入口文件传入store

import React from 'react'

import ReactDom from 'react-dom'

import { AppContainer } from 'react-hot-loader'

import { Provider } from 'react-redux'

import store from './redux/store'

import getRouter from 'router/router'

/* 初始化 */

renderWithHotReload(getRouter())

/* 热更新 */

if (module.hot) {

  module.hot.accept('./router/router', () => {

    const getRouter = require('router/router').default

    renderWithHotReload(getRouter())

  })

}

function renderWithHotReload (RootElement) {

  ReactDom.render(

    <AppContainer>

      <Provider store={store}>

        {RootElement}

      </Provider>

    </AppContainer>,

    document.getElementById('app')

  )

}

执行npm start,打开localhost:8080/counter

12 devtool优化

增加webpack配置devtool!可以查看浏览器报错的详细代码位置

src/webpack.dev.config.js增加devtool: ‘inline-source-map’

13  编译css

cnpm install css-loader style-loader --save-dev

css-loader使你能够使用类似@import 和 url(…)的方法实现 require()的功能;

style-loader将所有的计算后的样式加入页面中; 二者组合在一起使你能够把样式表嵌入webpack打包后的JS文件中。

修改page1.js


Page.css

测试查看page1页面效果

14 编译图片

cnpm install --save-dev url-loader file-loader

webpack.dev.config.js rules增加

{

        test: /\.(png|jpg|gif)$/,

        use: [{

          loader: 'url-loader',

          options: {

            limit: 8192

          }

        }]

      },

options limit 8192意思是,小于等于8K的图片会被转成base64编码,直接插入HTML中,减少HTTP请求。

引用图片,查看效果

修改page1.js

15 按需加载

打包完后,所有页面只生成了一个build.js,当我们首屏加载的时候,就会很慢。因为也下载了别的页面的js。

如果每个页面都打包了自己单独的JS,在进入自己页面的时候才加载对应的js,那首屏加载就会快很多。

cnpm install bundle-loader --save-dev

创建src/router/Bundle.js

import React, {

  Component

} from 'react'

class Bundle extends Component {

  state = {

    // short for "module" but that's a keyword in js, so "mod"

    mod: null

  };

  UNSAFE_componentWillMount() {

    this.load(this.props)

  }

  UNSAFE_componentWillReceiveProps(nextProps) {

    if (nextProps.load !== this.props.load) {

      this.load(nextProps)

    }

  }

  load(props) {

    this.setState({

      mod: null

    });

    props.load((mod) => {

      this.setState({

        // handle both es imports and cjs

        mod: mod.default ? mod.default : mod

      })

    })

  }

  render() {

    return this.props.children(this.state.mod)

  }

}

export default Bundle;

修改路由文件src/router/router.js

import React from 'react'

import { Route, Switch } from 'react-router-dom'

import Bundle from './Bundle'

import Home from 'bundle-loader?lazy&name=home!pages/Home/Home'

import Page1 from 'bundle-loader?lazy&name=page1!pages/Page1/Page1'

import Counter from 'bundle-loader?lazy&name=counter!pages/Counter/Counter'

import Loading from 'components/Loading/Loading'

const createComponent = (component) => (props) => (

  <Bundle load={component}>

    {

      (Component) => Component ? <Component {...props} /> : <Loading />

    }

  </Bundle>

)

const getRouter = () => (

  <div className='margin'>

    <Switch>

      <Route exact path='/' component={createComponent(Home)} />

      <Route path='/page1' component={createComponent(Page1)} />

      <Route path='/counter' component={createComponent(Counter)} />

    </Switch>

  </div>

)

export default getRouter

npm run start,打开浏览器,进入新的页面,都会加载自己的JS,但是名字都是0.bundle.js,分不清哪个页面的js,所以修改配置文件

output: {

    path: path.join(__dirname, './dist'),

    filename: '[name].[chunkhash].js',

    chunkFilename: '[name].[chunkhash].js',

    publicPath: '/'

  },

运行发现名字变成home.js(router.js里面import Home from ‘bundle-loader?lazy&name=home!pages/Home/Home’;这里name=home)

16 缓存

为了让用户第二次访不重复下载js,所以要做个缓存,但缓存后,我们修改代码,更新线上后,用户还是之前的js,那就会出bug

所以代码更新后,最好使打包生成的名字不一样。比如第一次叫home.a.js,第二次叫home.b.js。

修改配置文件

output: {

  path: path.join(__dirname, './dist'),

  filename: '[name].[hash].js',

  chunkFilename: '[name].[chunkhash].js'

}

打包都用增加hash,修改了文件,打包后相应的文件名字也改变了

17 HtmlWebpackPlugin

这个插件,每次会自动把js插入到你的模板index.html里面

cnpm install html-webpack-plugin --save-dev

新建模板src/index.html

<!DOCTYPE html>

<html lang="en">

<head>

  <meta charset="UTF-8">

  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <title>test2</title>

</head>

<body>

  <div id="app"></div>

</body>

</html>

修改webpack.dev.config.js,增加plugin

new HtmlWebpackPlugin({

      filename: 'index.html',

      template: path.join(__dirname, 'src/index.html')

    }),

18 提取公共代码

打包后bundle.js里面包含了react,redux,react-router等等公共库,这些代码基本上不会改变的。但是,他们合并在bundle.js里面,每次项目发布,重新请求bundle.js的时候,相当于重新请求了react等这些公共库所以把react这些不会改变的公共库提取出来,用户缓存下来。从此以后,用户再也不用下载这些库了

const webpack = require('webpack')

entry: {

    app: [

      path.join(__dirname, 'src/index.js')

    ],

    vendor: ['react', 'react-router-dom', 'redux', 'react-dom', 'react-redux']

  },

// plugins

new webpack.optimize.CommonsChunkPlugin({

      name: 'vendor'

    }),

把react等库生成打包到vendor.hash.js里面去。

但是发现编译生成的文件app.[hash].js和vendor.[hash].js生成的hash一样的,因为每次修改代码,都会导致vendor.[hash].js名字改变,那我们提取出来的意义也就没了。其实文档上写的很清楚

output: {

  path: path.join(__dirname, './dist'),

  filename: '[name].[hash].js', //这里应该用chunkhash替换hash

  chunkFilename: '[name].[chunkhash].js'

}

但是如果用chunkhash,会报错。和webpack-dev-server –hot不兼容,

所以配置正式版webpack.config.js

新建webpack.config.js

先删除webpack-dev-server相关的东西

devtool的值改成cheap-module-source-map

hash改成chunkhash

const path = require('path')

var HtmlWebpackPlugin = require('html-webpack-plugin')

var webpack = require('webpack')

module.exports = {

  devtool: 'cheap-module-source-map',

  entry: {

    app: [

      path.join(__dirname, 'src/index.js')

    ],

    vendor: ['react', 'react-router-dom', 'redux', 'react-dom', 'react-redux']

  },

  output: {

    path: path.join(__dirname, './dist'),

    filename: '[name].[chunkhash].js',

    chunkFilename: '[name].[chunkhash].js'

  },

  module: {

    rules: [{

      test: /\.js$/,

      use: ['babel-loader'],

      include: path.join(__dirname, 'src')

    }, {

      test: /\.css$/,

      use: ['style-loader', 'css-loader']

    }, {

      test: /\.(png|jpg|gif)$/,

      use: [{

        loader: 'url-loader',

        options: {

          limit: 8192

        }

      }]

    }]

  },

  plugins: [

    new HtmlWebpackPlugin({

      filename: 'index.html',

      template: path.join(__dirname, 'src/index.html')

    }),

    new webpack.optimize.CommonsChunkPlugin({

      name: 'vendor'

    })

  ],

  resolve: {

    alias: {

      pages: path.join(__dirname, 'src/pages'),

      component: path.join(__dirname, 'src/component'),

      router: path.join(__dirname, 'src/router'),

      actions: path.join(__dirname, 'src/redux/actions'),

      reducers: path.join(__dirname, 'src/redux/reducers')

    }

  }

}

在package.json增加打包脚本

"build": "webpack --config webpack.config.js"

执行npm run build~看看dist文件夹

19 文件压缩

cnpm i --save-dev uglifyjs-webpack-plugin

修改配置文件

var UglifyJSPlugin = require('uglifyjs-webpack-plugin')

// plugins

new UglifyJSPlugin(),

npm run build发现打包文件大小减小了好多

20 指定环境

许多 library 将通过与 process.env.NODE_ENV 环境变量关联,以决定 library 中应该引用哪些内容。例如,当不处于生产环境中时,某些 library 为了使调试变得容易,可能会添加额外的日志记录(log)和测试(test)。其实,当使用 process.env.NODE_ENV === ‘production’ 时,一些 library 可能针对具体用户的环境进行代码优化,从而删除或添加一些重要代码。我们可以使用 webpack 内置的 DefinePlugin 为所有的依赖定义这个变量:

// plugins
new webpack.DefinePlugin({

      'process.env': {

        'NODE_ENV': JSON.stringify('production')

      }

    }),

npm run build后发现vendor.[hash].js又变小了。

21 优化缓存

刚才我们把[name].[hash].js变成[name].[chunkhash].js后,npm run build后,发现app.xxx.js和vendor.xxx.js不一样。

随便修改代码一处,例如Home.js,随便改变个字,你发现home.xxx.js名字变化的同时,vendor.xxx.js名字也变了。这不行啊。这和没拆分不是一样一样了吗?我们希望是vendor.xxx.js名字永久不变,一直缓存在用户本地的。

官方文档推荐了一个插件HashedModuleIdsPlugin

plugins: [

  new webpack.HashedModuleIdsPlugin(),

  new webpack.optimize.CommonsChunkPlugin({ name: 'runtime' }) //  引入顺序在这里很重要。CommonsChunkPlugin 的 ‘vendor’ 实例,必须在 ‘runtime’ 实例之前引入。

]

22 打包优化

cnpm install clean-webpack-plugin --save-dev

修改webpack.config.js

const CleanWebpackPlugin = require('clean-webpack-plugin');

plugins: [

    new CleanWebpackPlugin(['dist'])

]

23 抽取css

cnpm install --save-dev extract-text-webpack-plugin

const ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {

  module: {

    rules: [

      {

        test: /\.css$/,

        use: ExtractTextPlugin.extract({

          fallback: "style-loader",

          use: "css-loader"

        })

      }

    ]

  },

  plugins: [

     new ExtractTextPlugin({

         filename: '[name].[contenthash:5].css',

         allChunks: true

     })

  ]

}

npm run build后发现单独生成了css文件

24 合并提取webpack公共配置

把公共的配置文件提取出来。提取到webpack.common.config.js里面~

webpack.dev.config.js和webpack.config.js写自己的特殊的配置。

这里我们需要用到webpack-merge来合并公共配置和单独的配置。

cnpm install --save-dev webpack-merge

新建webpack.common.config.js

const path = require('path');

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

const webpack = require('webpack');

commonConfig = {

    entry: {

        app: [

            path.join(__dirname, 'src/index.js')

        ],

        vendor: ['react', 'react-router-dom', 'redux', 'react-dom', 'react-redux']

    },

    output: {

        path: path.join(__dirname, './dist'),

        filename: '[name].[chunkhash].js',

        chunkFilename: '[name].[chunkhash].js',

        publicPath: "/"

    },

    module: {

        rules: [{

            test: /\.js$/,

            use: ['babel-loader?cacheDirectory=true'],

            include: path.join(__dirname, 'src')

        }, {

            test: /\.(png|jpg|gif)$/,

            use: [{

                loader: 'url-loader',

                options: {

                    limit: 8192

                }

            }]

        }]

    },

    plugins: [

        new HtmlWebpackPlugin({

            filename: 'index.html',

            template: path.join(__dirname, 'src/index.html')

        }),

        new webpack.HashedModuleIdsPlugin(),

        new webpack.optimize.CommonsChunkPlugin({

            name: 'vendor'

        }),

        new webpack.optimize.CommonsChunkPlugin({

            name: 'runtime'

        })

    ],

    resolve: {

        alias: {

            pages: path.join(__dirname, 'src/pages'),

            components: path.join(__dirname, 'src/components'),

            router: path.join(__dirname, 'src/router'),

            actions: path.join(__dirname, 'src/redux/actions'),

            reducers: path.join(__dirname, 'src/redux/reducers')

        }

    }

};

module.exports = commonConfig;

修改 webpack.dev.config.js

const merge = require('webpack-merge');

const path = require('path');

const commonConfig = require('./webpack.common.config.js');

const devConfig = {

    devtool: 'inline-source-map',

    entry: {

        app: [

            'react-hot-loader/patch',

            path.join(__dirname, 'src/index.js')

        ]

    },

    output: {

        /*这里本来应该是[chunkhash]的,但是由于[chunkhash]和react-hot-loader不兼容。只能妥协*/

        filename: '[name].[hash].js'

    },

    module: {

        rules: [{

            test: /\.css$/,

            use: ["style-loader", "css-loader"]

        }]

    },

    devServer: {

        contentBase: path.join(__dirname, './dist'),

        historyApiFallback: true,

        host: '0.0.0.0',

    }

};

module.exports = merge({

    customizeArray(a, b, key) {

        /*entry.app不合并,全替换*/

        if (key === 'entry.app') {

            return b;

        }

        return undefined;

    }

})(commonConfig, devConfig);

修改webpack.config.js

const merge = require('webpack-merge');

const webpack = require('webpack');

const UglifyJSPlugin = require('uglifyjs-webpack-plugin');

const CleanWebpackPlugin = require('clean-webpack-plugin');

const ExtractTextPlugin = require("extract-text-webpack-plugin");

const commonConfig = require('./webpack.common.config.js');

const publicConfig = {

    devtool: 'cheap-module-source-map',

    module: {

        rules: [{

            test: /\.css$/,

            use: ExtractTextPlugin.extract({

                fallback: "style-loader",

                use: "css-loader"

            })

        }]

    },

    plugins: [

        new CleanWebpackPlugin(['dist/*.*']),

        new UglifyJSPlugin(),

        new webpack.DefinePlugin({

            'process.env': {

                'NODE_ENV': JSON.stringify('production')

            }

        }),

        new ExtractTextPlugin({

            filename: '[name].[contenthash:5].css',

            allChunks: true

        })

    ]

};

module.exports = merge(commonConfig, publicConfig);

25 优化目录结构并增加404页面

优化下目录结构,把router和nav分开,新建根组件App

新建根组件components/App/APP.js

import React, {Component} from 'react';

import Nav from 'components/Nav/Nav';

import getRouter from 'router/router';

export default class App extends Component {

    render() {

        return (

            <div>

                <Nav/>

                {getRouter()}

            </div>

        )

    }

}

新建components/Nav/Nav组件,把router/router.js里面的nav提出来。

新建components/Loading/Loading组件,把router/router.js里面的Loading提出来。

入口文件src/index.js修改

import React from 'react';

import ReactDom from 'react-dom';

import {AppContainer} from 'react-hot-loader';

import {Provider} from 'react-redux';

import store from './redux/store';

import {BrowserRouter as Router} from 'react-router-dom';

import App from 'components/App/App';

renderWithHotReload(App);

if (module.hot) {

    module.hot.accept('components/App/App', () => {

        const NextApp = require('components/App/App').default;

        renderWithHotReload(NextApp);

    });

}

function renderWithHotReload(RootElement) {

    ReactDom.render(

        <AppContainer>

            <Provider store={store}>

                <Router>

                    <RootElement/>

                </Router>

            </Provider>

        </AppContainer>,

        document.getElementById('app')

    )

}

新建pages/NotFound/NotFound组件。

修改router/router.js,增加404

import NotFound from 'bundle-loader?lazy&name=notFound!pages/NotFound/NotFound';

<Route component={createComponent(NotFound)}/>

26 集成PostCSS

npm install --save-dev postcss-loader

npm install --save-dev postcss-cssnext //  postcss-cssnext允许你使用未来的 CSS 特性(包括 autoprefixer)

修改webpack配置文件,增加postcss-loader

修改webpack.config.js

rules: [{

  test: /\.css$/,

  use: ExtractTextPlugin.extract({

      fallback: "style-loader",

      use: ["css-loader", "postcss-loader"]

  })

}]

修改webpack.dev.config.js

rules: [{

  test: /\.(css|scss)$/,

  use: ["style-loader", "css-loader", "postcss-loader"]

}]

根目录增加postcss配置文件。

module.exports = {

  plugins: {

    'postcss-cssnext': {}

  }

}

先这些叭没有了


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342