搭建 webpack + React 開發環境

說在開頭css

上個月斷斷續續的在研究webpack的配置,可是不少網上的文章基本上都是隻說了開發環境的配置,而忽略了生產環境的配置。大體研究了一下門路,而後就來寫一篇隨筆讓本身能在之後能有個地方能夠作參考。html

正文開始node

我就僞裝你們都是裝了node的狀況下。
react

一、進入項目目錄,運行`npm init`按照步驟填寫最終生成`package.json`文件,全部使用 npm 作依賴管理的項目,根目錄下都會有一個這個文件,該文件描述了項目的基本信息以及一些第三方依賴項(插件)。詳細的使用說明可查閱[ 官網文檔],不過是英文的。
 
二、已知咱們將使用 webpack 做爲構建工具,那麼就須要安裝相應插件,運行 `npm install webpack webpack-dev-server --save-dev` 來安裝兩個插件。
又已知咱們將使用 React ,也須要安裝相應插件,運行 `npm i react react-dom --save`來安裝兩個插件。其中`i`是`install`的簡寫形式。安裝完成以後,查看`package.json`可看到多了`devDependencies`和`dependencies`兩項,根目錄也多了一個`node_modules`文件夾。
 
三、`--save` 和 `--save-dev` 的區別
`npm i`時使用`--save`和`--save-dev`,可分別將依賴(插件)記錄到`package.json`中的`dependencies`和`devDependencies`下面。
`dependencies`下記錄的是項目在運行時必須依賴的插件,常見的例如`react` `jquery`等,即便項目打包好了、上線了,這些也是須要用的,不然程序沒法正常執行。
`devDependencies`下記錄的是項目在開發過程當中使用的插件,例如這裏咱們開發過程當中須要使用`webpack`打包,可是一旦項目打包發佈、上線了以後,`webpack`就都沒有用了,可卸磨殺驢。
延伸一下,咱們的項目有`package.json`,其餘咱們用的項目如`webpack`也有`package.json`,見`./node_modules/webpack/package.json`,其中也有`devDependencies`和`dependencies`。當咱們使用`npm i webpack`時,`./node_modules/webpack/package.json`中的`dependencies`會被 npm 安裝上,而`devDependencies`也不必安裝。
 
四、webpack.config.js
爲了提升學習效率,webpack 最最基礎的用法,就再也不挨個演示了(推薦一個很是好的[ 入門文章],以及[ 更多資料])這裏咱們把項目中的`webpack.config.js`這個配置文件詳細的講解一下,過程當中也會照顧 0 基礎的同窗。
webpack.config.js 就是一個普通的 js 文件,符合 commonJS 規範。最後輸出一個對象,即`module.exports = {...}`
這個比較基礎,不過須要新建`./app/index.js`做爲入口文件,目前項目中只有這一個入口文件。不過 webpack 支持多個入口文件,可查閱文檔。
輸出就是一個`bundle.js`,js 和 css 都在裏面,不過只有在開發環境下才用,發佈代碼的時候,確定不能只有這麼一個文件,接下來會講到。
 
module
針對不一樣類型的文件,使用不一樣的`loader`,固然使用以前要安裝,例如`npm i style-loader css-loader --save-dev`。該項目代碼中,咱們用到的文件格式有:js/jsx 代碼、css/less 代碼、圖片、字體文件。
less 是 css 的語法糖,能夠更高效低冗餘的寫 css,不熟悉的朋友可去[ 官網]看看,很是簡單。
在加載 css/less 時用到了`postcss`,主要使用`autoprefixer`功能,幫助自動加 css3 的瀏覽器前綴,很是好用。
編譯 es6 和 jsx 語法時,用到家喻戶曉的`babel`,另外還需增長一個`.babelrc`的配置文件。
plugins
使用 html 模板(須要`npm i html-webpack-plugin --save-dev`),這樣能夠將輸出的文件名(如`./bundle.js`)自動注入到 html 中,不用咱們本身手寫。手寫的話,一旦修改就須要改兩個地方。
使用熱加載和自動打開瀏覽器插件
devServer
對 webpack-dev-server 的配置
npm start
在 package.json 中,輸入如下代碼,將這一串命令封裝爲`npm start`,這樣就能夠運行項目代碼了。
 
"scripts": {
"start": "NODE_ENV=dev webpack-dev-server --progress --colors"
}
 
代碼中`NODE_ENV=dev`表明當前是開發環境下,這裏的`"dev"`可被 js 代碼中的`process.env.NODE_ENV`獲得並作一些其餘處理。
 
五、定義環境全局變量
如下定義,可以使得代碼經過`__DEV__`獲得當前是否是開發模式。

new webpack.DefinePlugin({
__DEV__: JSON.stringify(JSON.parse((process.env.NODE_ENV == 'dev') || 'false'))
})

 


打開`./app/util/localStore.js`能夠看到`if (__DEV__) { console.error('localStorage.getItem報錯, ', ex.message) }`,即只有開發環境下才提示error,發佈以後就不會提示了。(由於發佈的命令中用到`NODE_ENV=production`)
 
六、生產環境的配置 webpack.production.config.js
開發環境下,能夠不用考慮系統的性能,更多考慮的是如何增長開發效率。而發佈系統時,就須要考慮發佈以後的系統的性能,包括加載速度、緩存等。下面介紹發佈用配置代碼和開發用的不同的地方。
發佈到 `./build` 文件夾中 : `path: __dirname + "/build",`
vendor
將第三方依賴單獨打包。即將 node_modules 文件夾中的代碼打包爲 vendor.js 將咱們本身寫的業務代碼打包爲 app.js。這樣有助於緩存,由於在項目維護過程當中,第三方依賴不常常變化,而業務代碼會常常變化。
md5後綴
爲每一個打包出來的文件都加md5後綴,例如`"/js/[name].[chunkhash:8].js"`,可以使文件強緩存。
分目錄
打包出來的不一樣類型的文件,放在不一樣目錄下,例如圖片文件將放在`img/`目錄下
Copyright
自動爲打包出來的代碼增長 copyright 內容
分模塊
`new webpack.optimize.OccurenceOrderPlugin(),`
壓縮代碼
使用 Uglify 壓縮代碼,其中`warnings: false`是去掉代碼中的 warning
分離 css 和 js 文件
開發環境下,css 代碼是放在整個打包出來的那個 bundle.js 文件中的,發佈環境下固然不能混淆在一塊兒,使用`new ExtractTextPlugin('/css/[name].[chunkhash:8].css'),`將 css 代碼分離出來。
 
七、npm run build
打開`package.json`,查看如下代碼。`npm start`和`npm run build`分別是運行代碼和打包項目。另外,`"start"、"test"`能夠不用`run`。

 
"scripts": {
"start": "NODE_ENV=dev webpack-dev-server --progress --colors",
"build": "rm -rf ./build && NODE_ENV=production webpack --config ./webpack.production.config.js --progress --colors"
},

 


這兩個命令主要有如下區別:

- 前者中默認使用 webpack.config.js 做爲配置文件,然後者中強制使用 webpack.production.config.js 做爲配置文件
- 前者`NODE_ENV=dev`然後者`NODE_ENV=production`,標識不一樣的環境。而這個`"dev" "production"`能夠在代碼中經過`process.env.NODE_ENV`獲取。
 
最小化壓縮 React
如下配置能夠告訴 React 當前是生產環境,請最小化壓縮 js ,即把開發環境中的一些提示、警告、判斷統統去掉,直流如下發布以後可用的代碼。

new webpack.DefinePlugin({
'process.env':{
'NODE_ENV': JSON.stringify(process.env.NODE_ENV)
}
}),

 接下來把開發代環境的配置和生產環境的配置貼上jquery

webpack.config.jswebpack

var path = require('path')
var webpack = require('webpack')
var HtmlWebpackPlugin = require('html-webpack-plugin');
var OpenBrowserPlugin = require('open-browser-webpack-plugin');

module.exports = {
    entry: path.resolve(__dirname, 'app/index.js'),
    output: {
        filename: "bundle.js"
    },
    resolve: {
        extensions: ['', '.js', '.jsx']
    },
    module: {
        loaders: [
            { test: /\.(js|jsx)$/, exclude: /node_modules/, loader: 'babel' },
            { test: /\.less$/, exclude: /node_modules/, loader: 'style!css!postcss!less' },
            { test: /\.css$/, exclude: /node_modules/, loader: 'style!css!postcss' },
            { test:/\.(png|gif|jpg|jpeg|bmp)$/i, loader:'url-loader?limit=5000' },  // 限制大小5kb
            { test:/\.(png|woff|woff2|svg|ttf|eot)($|\?)/i, loader:'url-loader?limit=5000'} // 限制大小小於5k
        ]
    },
    postcss: [
        require('autoprefixer') //調用autoprefixer插件,例如 display: flex
    ],
    plugins: [
        // html 模板插件
        new HtmlWebpackPlugin({
            template: __dirname + '/app/index.html'
        }),
        //  熱加載
        new webpack.HotModuleReplacementPlugin(),
        // 打開瀏覽器
        new OpenBrowserPlugin({
            url: 'http://localhost:8080'
        }),
        //  可在業務js代碼中使用 __DEV__ 判斷是不是開發環境 (dev模式下能夠提示錯誤、測試報告等, production模式不提示)
        new webpack.DefinePlugin({
            __DEV__: JSON.stringify(JSON.parse((process.env.NODE_ENV == 'dev') || 'false')),
        })
    ],
    devServer: {
        proxy: {
          // 凡是 `/api` 開頭的 http 請求,都會被代理到 localhost:3000 上,由 koa 提供 mock 數據。
          // koa 代碼在 ./mock 目錄中,啓動命令爲 npm run mock
          '/api': {
            target: 'http://localhost:3000',
            secure: false
          }
        },
        colors: true, // 終端舒服爲彩色
        historyApiFallback: true, //不跳轉,在開發單頁應用時很是有用,它依賴於HTML5 history API,若是設置爲true,全部的跳轉將指向index.html
        inline: true, // 實時刷新
        hot: true, // 使用熱加載插件 HotModuleReplacementPlugin
    }
}

webpack.production.config.jscss3

var pkg = require('./package.json')
var path = require('path')
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
  entry: {
    app: path.resolve(__dirname, 'app/index.js'),
    // 將 第三方依賴(node_modules中的) 單獨打包
    vendor: Object.keys(pkg.dependencies)
  },
  output: {
    path: __dirname + "/build",
    filename: "/js/[name].[chunkhash:8].js"
  },

  resolve:{
      extensions:['', '.js','.jsx']
  },

  module: {
    loaders: [
        { test: /\.(js|jsx)$/, exclude: /node_modules/, loader: 'babel' },
        { test: /\.less$/, exclude: /node_modules/, loader: ExtractTextPlugin.extract('style', 'css!postcss!less') },
        { test: /\.css$/, exclude: /node_modules/, loader: ExtractTextPlugin.extract('style', 'css!postcss') },
        { test:/\.(png|gif|jpg|jpeg|bmp)$/i, loader:'url-loader?limit=5000&name=img/[name].[chunkhash:8].[ext]' },
        { test:/\.(png|woff|woff2|svg|ttf|eot)($|\?)/i, loader:'url-loader?limit=5000&name=fonts/[name].[chunkhash:8].[ext]'}
    ]
  },
  postcss: [
    require('autoprefixer')
  ],

  plugins: [
    // webpack 內置的 banner-plugin
    new webpack.BannerPlugin("Copyright by Nick930826@github.com."),

    // html 模板插件
    new HtmlWebpackPlugin({
        template: __dirname + '/app/index.html'
    }),

    // 定義爲生產環境,編譯 React 時壓縮到最小
    new webpack.DefinePlugin({
      'process.env':{
        'NODE_ENV': JSON.stringify(process.env.NODE_ENV)
      }
    }),

    // 爲組件分配ID,經過這個插件webpack能夠分析和優先考慮使用最多的模塊,併爲它們分配最小的ID
    new webpack.optimize.OccurenceOrderPlugin(),

    new webpack.optimize.UglifyJsPlugin({
        compress: {
          //supresses warnings, usually from module minification
          warnings: false
        }
    }),

    // 分離CSS和JS文件
    new ExtractTextPlugin('/css/[name].[chunkhash:8].css'),

    // 提供公共代碼
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      filename: '/js/[name].[chunkhash:8].js'
    }),

    // 可在業務 js 代碼中使用 __DEV__ 判斷是不是dev模式(dev模式下能夠提示錯誤、測試報告等, production模式不提示)
    new webpack.DefinePlugin({
      __DEV__: JSON.stringify(JSON.parse((process.env.NODE_ENV == 'dev') || 'false'))
    })
  ]
}
相關文章
相關標籤/搜索