webpack腳手架構建React項目

lesson-1主要內容:構建一套適合 React、ES6 開發的腳手架

項目地址 github-react-lessoncss

Features

  • 能夠解析JSX語法html

  • 能夠解析ES6語法新特性前端

  • 支持LESS、SCSS預處理器node

  • 編譯完成自動打開瀏覽器react

  • 單獨分離CSS樣式文件webpack

  • 支持文件MD5戳,解決文件緩存問題css3

  • 支持圖片、圖標字體等資源的編譯git

  • 支持瀏覽器源碼調試github

  • 實現組件級熱更新web

  • 實現代碼的熱替換,瀏覽器實時刷新查看效果

  • 區分開發環境和生產環境

  • 分離業務功能代碼和公共依賴代碼

前言:

Webpack 是當下最熱門的前端資源模塊化管理和打包工具。它能夠將許多鬆散的模塊按照依賴和規則打包成符合生產環境部署的前端資源。
還能夠將按需加載的模塊進行代碼分隔,等到實際須要的時候再異步加載。經過 loader 的轉換,任何形式的資源均可以視做模塊,好比
CommonJs 模塊、 AMD 模塊、 ES6 模塊、CSS、圖片、 JSON、Coffeescript、 LESS 等。

Webpack 和 gulp 沒有可比性

也許你用過gulp,有了gulp你能夠對css ,js 文件進行壓縮合並等處理,但隨着前端技術不斷髮展,出現了前端資源模塊化,資源按需
加載,ES6模塊等,利用gulp來構建你的項目就顯得力不從心了.若是非要作個對比的話,下面給出兩張工做流程圖片就明白了

Grunt和Gulp的工做方式是:在一個配置文件中,指明對某些文件進行相似編譯,組合,壓縮等任務的具體步驟,這個工具以後能夠自動替你完成這些任務。

img1

Webpack的工做方式是:把你的項目當作一個總體,經過一個給定的主文件(如:index.js),Webpack將從這個文件開始找到你的項目的全部依賴文件,使用loaders處理它們,最後打包爲一個瀏覽器可識別的JavaScript文件。

img2

項目構建,安裝必要包

clone git@github.com:ZengTianShengZ/react-lesson.git

切到 在lesson-1 的根目錄下執行如下命令,安裝必要包

npm install
 // 若是安裝過 npm 淘寶鏡像
 cnpm install
 // 若是是 Mac 須要權限
 sudo npm install

開發過程當中你會用到如下命令進行打包編譯:

| lesson-1根目錄下運行命令行 | 解釋 |
| ------------- |:-------------:|
| webpack | 該命令會默認找到根目錄下 webpack.config.js 文件並運行|
| npm run dev | 改命令會找到 根目錄下的 package.json 文件 並運行 文件下的 scripts 下的 dev命令,其實也就執行 node server.js |
| npm run hot | 改命令是同上 實際上是執行 node server.hot.js |
| npm run build | 改命令是同上 ,會執行 webpack --config webpack.config.dist.js --progress --colors --watch -p| ##

1、 webpack 基礎講解

lesson-1 根目錄下 執行 ‘ webpack ’ 命令
執行該命令會執行 根目錄下 webpack.config.js ,其實這裏是爲了講解 webpack 的工做原理和演示
項目用的最多的是執行 webpack.config.hot.js 和 webpack.config.build.js 後面會作講解

webpack.config.js 大體流程圖:

img3

一、entry

entry: {
        app: APP_FILE
    }

指定一個入口文件,webpack將會順藤摸瓜識別所依賴的文件,再一個個進行接下去的解析處理

二、output

output: {
        publicPath: './static/',     //編譯好的文件,在服務器的路徑,這是靜態資源引用路徑
        path: BUILD_PATH,            //編譯到當前目錄
        filename: '[name].js',       //編譯後的文件名字
        chunkFilename: '[name].[chunkhash:5].min.js',
    },

指定一個出口路徑,當webpack處理完依賴文件後,將輸出文件輸出到指定路徑下
其中:

filename: '[name].js'

指輸出 js 文件名 同 entry: 的輸入文件名的配置同樣,這裏是會輸出 app.js

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

會對輸出的文件添加後綴 , 一個5位的 hash 值

三、devtool

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

除了輸出編譯後的文件外,還會順帶輸出一個 Source Map 。什麼是 Source Map呢,Source map就是一個信息文件,裏面儲存着位置信息。也就是說,轉換後的代碼的每個位置,所對應的轉換前的位置。如轉碼後的 ES6文件或 React的jsx文件 當代碼出錯咱們很難找到對應的出錯位置,那
Source Map 就提供了一個對應關係,來指出錯誤的位置。

四、resolve

resolve: {
        extensions: ['', '.js', '.jsx', '.less', '.scss', '.css'], //後綴名自動補全
    }

這個配置能夠幫咱們自動補全後綴名,當咱們 import 一個 demo.js 時能夠這麼寫不帶後綴的

import demo from 'demo';

五、module

module: {
      loaders: [{
          test: /\.scss$/,
          exclude: /^node_modules$/,
          loader: ExtractTextPlugin.extract('style', ['css', 'autoprefixer', 'sass']),
          include: [APP_PATH]
      },{
          test: /\.js$/,
          exclude: /^node_modules$/,
          loader: 'babel',
          include: [APP_PATH]
      },{
          test: /\.(png|jpg)$/,
          exclude: /^node_modules$/,
          loader: 'url-loader?limit=8192&name=images/[hash:8].[name].[ext]',
          //注意後面那個limit的參數,當你圖片大小小於這個限制的時候,會自動啓用base64編碼圖片
          include: [APP_PATH]
      }, {
          test: /\.jsx$/,
          exclude: /^node_modules$/,
          loaders: ['jsx', 'babel'],
          include: [APP_PATH]
      }]
  },

webpack 的核心部分就是各類 loader 了 ,webpack 拿到入口文件,並順藤摸瓜解析到各類依賴文件,遇到不一樣的文件後綴就執行對應的loader進行處理
其中

test: /.scss$/,

當依賴文件有以 .scss 後綴的文件,會先執行 sass-loader 再 執行 autoprefixer-loader(給一些css3添加後綴的loader)
接着執行 css-loader ,這才轉化成立 .css 文件,才能做用於瀏覽器,而 style-loader 是將 .css 文件插入到 html的 head 頭部
也許你會注意到 ExtractTextPlugin() 包裹一堆loader是幹嗎的,這個放着下面 webpack的插件這一節講

test: /.js$/,

當依賴文件有以 .js 後綴的文件 ,會通過 loader: 'babel', 這裏多作了一步跳轉是 babel會找到根目錄下我們配置的一個 .babelrc文件

{
      "presets": [
          "react",
          "es2015",
           "stage-0",
      ],
      "plugins": [
        "transform-decorators-legacy",
        "transform-class-properties"
    ]
}

.js 文件會根據上面的配置進行解析

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

{
    test: /\.(png|jpg)$/,
    exclude: /^node_modules$/,
    loader: 'url-loader?limit=8192&name=images/[hash:8].[name].[ext]',
    //注意後面那個limit的參數,當你圖片大小小於這個限制的時候,會自動啓用base64編碼圖片
    include: [APP_PATH]
}

圖片幹嗎須要 loader呢,上面也解釋了 能夠將一個較小的圖片進行 base64轉換

test: /.jsx$/,

React 獨有的 .jsx 文件 ,相對 .js文件多了一步 jsx-loader

六、plugins

plugins: [
        new webpack.DefinePlugin({
            'process.env': {
                NODE_ENV: JSON.stringify('development')    //定義編譯環境
            },
            'cdnUrl':JSON.stringify('http:demo.com/'),
            'dev': true
        }),
        new HtmlWebpackPlugin({                            //根據模板插入css/js等生成最終HTML
            filename: '../index.html',                     //生成的html存放路徑,相對於 path
            template: './src/template/index.html',         //html模板路徑
            hash: false,
        }),
        new ExtractTextPlugin('[name].css')
    ],

其中:

DefinePlugin

plugins: [
        new webpack.DefinePlugin({
            'process.env': {
                NODE_ENV: JSON.stringify('development')    //定義編譯環境
            },
            'cdnUrl':JSON.stringify('http:demo.com/'),
            'dev': true
        })
    ]

定義一下全局變量,能夠在模塊中使用這些變量
如一個項目中依賴的 demo.js 文件

console.log(cdnUrl);

編譯後的 demo.js 輸出:

console.log(‘http:demo.com/’);

這個全局變量有什麼做用呢,若是你有點項目經驗就知道這個 plugin 的巨大好處了,舉個小例子,我們在項目開發時,線上環境
和開發環境是不同的,好比 cdn資源路徑,就能夠定義全局變量來更改全局路徑資源:

if(dev){ // dev 是 DefinePlugin 的一個全局變量

}else{

}

HtmlWebpackPlugin

plugins: [
        new HtmlWebpackPlugin({                     //根據模板插入css/js等生成最終HTML
            filename: '../index.html',              //生成的html存放路徑,相對於 path
            template: './src/template/index.html',  //html模板路徑
            hash: false,
        })
    ],

依賴文件通過 webpack 編譯完輸出到指定的目錄下 ,那怎麼被 html 引用呢,那就須要 HtmlWebpackPlugin 插件。
用法上面有註釋就很少說。

ExtractTextPlugin

plugins: [
        new ExtractTextPlugin('[name].css')
    ],

咱們會在 .js 文件 import .css 或 .scss 文件,webpack 編譯 .js 文件時會將這個css文件打包進了 js文件裏頭。
但有時咱們的 css文件比較大或想單獨拿出來,那就能夠利用這個插件 ExtractTextPlugin ,目的是生成單獨的一份 css
文件,而不是打包到 .js 文件裏頭

編譯前:

img5

編譯後:

img6

2、開發環境下的 webpack -- 熱刷新

在實際開發中不可能編譯一下webpack 在瀏覽器刷新看一下結果,編輯完再編譯一下webpack,再刷新瀏覽器看一下效果。這樣工做效率很是低
也很不爽。
下面我們就來構建一套實際開發時的 webpack 來構建項目。webpack 的配置總體上跟第一節講的差很少,主要在熱部署上多作些處理而已。

一、啓動熱刷新

與第一節不一樣的是,咱們不是經過命令行 webpack 來啓動編譯,而是經過一個service服務 。能夠在lesson-1 根目錄下執行命令

npm run hot 或 node server_hot.js // 其實 npm run hot 也是找的跟目錄下的 package.json 執行 node server_hot.js 的

執行完命令 試着改變同樣 js文件或scss文件保存一下,發現瀏覽器頁面是自動刷新的。但 build 目錄下卻不見得有任何輸出。
這是由於咱們使用了 熱刷新 的一個中間件 ,每次保存完文件會自動編譯項目中依賴的 js 和 css 文件,編譯完的輸出文件輸出到
計算機的內存中 這樣在開發的過程當中不用每次讀寫硬盤,速度也會快不少

二、中間件 webpack-dev-middleware

上面第一小點提到一個中間件,那這個中間件到底怎麼工做的呢,會使得我們在開發過程當中熱刷新。或者說 執行命令行 npm run hot
都作了哪些。下面一幅圖帶你理解:

img7

三、 npm run hot

運行命令行 npm run hot 其中就是執行 server_hot.js 這個文件,啓動一個 server ,裏面涉及到 nodejs的一些知識和Node.js Express 框架 ,對這一塊不太熟悉的能夠看這裏 Node.js 教程| 菜鳥教程

(1)、分析 server_hot.js
var webpack = require('webpack');
var express = require('express');
var config = require('./webpack.config.hot');

var app = express();
var compiler = webpack(config);

app.use(require('webpack-dev-middleware')(compiler, {
    publicPath: config.output.publicPath,
    hot: true,
    historyApiFallback: true,
    inline: true,
    progress: true,
    stats: {
    colors: true,
    }
}));

app.use(require('webpack-hot-middleware')(compiler));自動刷新的消息通知依靠的是瀏覽器和服務器之間的web socket鏈接

//將其餘路由,所有返回index.html
app.get('*', function(req, res) {
    res.sendFile(__dirname + '/index.html')
});

app.listen(8088, function() {
    console.log('正常打開8088端口')
});
(2)、看一下下面例子解釋一下 app.use()
var express = require('express');
var app = express();
app.use(function (req, res, next) {   // 沒指定路徑默認是 app.use('/',function(){}) 訪問根路徑                                                    //會進入這個函數
  console.log('Time:', Date.now());
  next();
});

也就是 當 服務端接收到一個請求時,會先被 app.use()攔截下,由於 app.use()沒有指定路徑,默認接收根路徑,
如 訪問 http://127.0.0.1:8088/ ,那麼網絡請求就會先被app.use()攔截下。

app.use()處理完事情就會交給 下面的 get 或 post 請求了:

//將其餘路由,所有返回index.html
app.get('*', function(req, res) {
    res.sendFile(__dirname + '/index.html')
});

這裏 get 收到請求後就給瀏覽器一個 響應(response) res.sendFile() 對瀏覽器輸出 index.html

(3)、看一下 webpack-dev-middleware 中間件
app.use(require('webpack-dev-middleware')(compiler, {
    publicPath: config.output.publicPath,
    hot: true,
    historyApiFallback: true,
    inline: true,
    progress: true,
    stats: {
    colors: true,
    }
}));

上面一小點說到了 app.use 會收到請求後先作一些處理 ,處理內容就是 webpack-dev-middleware 來完成,
這也是熱更新的關鍵。自動刷新的消息通知依靠的是瀏覽器和服務器之間的 web socket 鏈接. 當保存一下文件(command+s或Ctrl+s)
瀏覽器就會經過 鏈接 向服務端發送請求,服務端接收請求後 先被 app.use()攔截下來,通過 webpack-dev-middleware 中間件
處理,處理完 交給 app.get()輸出 index.html 到瀏覽器,至此,瀏覽器自動刷新完成!

其中 webpack-dev-middleware 中間件 接收兩個參數,一個是 webpack(config) 這就是用於編譯 js css 的配置文件了,我們在第一大節已經介紹了,裏面的內容跟第一大節差很少,幾處修改後面會解釋。第二個參數是一個配置對象 具體看github-webpack-dev-middleware

(4)、 最後看一下 webpack-hot-middleware
app.use(require('webpack-hot-middleware')(compiler));

若是一些文件的小改動好比 改變一個 div 的顏色啊,都有通過一大堆的編譯那效率就過低了,因此 webpack-hot-middleware 能夠對一些小
改動快速刷新瀏覽器,配合 webpack-dev-middleware 使用。

四、 熱跟新的配置文件 webpack.config.hot.js

這個配置文件其實跟第一大節講的並沒有太大區別,只不過要配合熱刷新須要新添加以下配置:
entry 添加 webpack-hot-middleware/client

entry: {
    app: [
        'webpack-hot-middleware/client',
        APP_FILE
    ]
},

plugins 添加以下:

plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin()
]

3、 線上環境下的 webpack

通常呢,項目開發完要發佈到服務器,是須要配合另外一套的項目打包流程的,發佈到服務器的項目是不須要熱更新等一些輔助開發的流程,
但同時根據項目的狀況須要加入一些好比 壓縮代碼,抽離公共代碼,異步加載js 等 需求。下面配合打包線上項目的 webpack.config.build.js
來講一下如何打包線上項目的。

一、npm run build

在lesson-1 根目錄下運行命令 npm run build ,根目錄會輸出如下文件:

img8

運行命令 npm run build 是找到 根目錄下的 package.json 文件執行 scripts 下的 build命令,其實就是執行:

"build": "webpack --config webpack.config.build.js --progress --colors --watch -p"

--config webpack.config.build.js 指定 命令執行的文件
--progress 指定在控制檯輸出進度條
--colors 控制檯顯示顏色

二、分析 webpack.config.build.js

var path = require('path');
var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin'); //css單獨打包
var HtmlWebpackPlugin = require('html-webpack-plugin'); //生成html

//定義地址
var ROOT_PATH = path.resolve(__dirname);
var APP_PATH = path.resolve(ROOT_PATH, 'src');              //__dirname 中的src目錄,以此類推
var APP_FILE = path.resolve(APP_PATH, 'app');               //根目錄文件app.jsx地址
var BUILD_PATH = path.resolve(ROOT_PATH, './build/static'); //發佈文件所存放的目錄/pxq/dist/前面加/報錯?

module.exports = {
    entry: {
        app: APP_FILE,
        common: [
            "react",
            'react-dom',
            'react-router',
            'redux',
            'react-redux',
            'redux-thunk',
            'immutable'
        ]
    },
    output: {
        //publicPath: 'http:example.cdn/',   // 給資源文件添加前綴,通常會把靜態資源發佈的 cdn 上
        path: BUILD_PATH,                    //編譯到當前目錄
        filename: '[name].js',               //編譯後的文件名字
        chunkFilename: '[name].[chunkhash:5].min.js',
    },
    resolve: {
        extensions: ['', '.js', '.jsx', '.less', '.scss', '.css'] //後綴名自動補全
    },
    module: {
        loaders: [{
            test: /\.js$/,
            exclude: /^node_modules$/,
            loader: 'babel'
        }, {
            test: /\.css$/,
            exclude: /^node_modules$/,
            loader: ExtractTextPlugin.extract('style', ['css', 'autoprefixer'])
        }, {
            test: /\.less$/,
            exclude: /^node_modules$/,
            loader: ExtractTextPlugin.extract('style', ['css', 'autoprefixer', 'less'])
        }, {
            test: /\.scss$/,
            exclude: /^node_modules$/,
            loader: ExtractTextPlugin.extract('style', ['css', 'autoprefixer', 'sass'])
        }, {
            test: /\.(eot|woff|svg|ttf|woff2|gif|appcache)(\?|$)/,
            exclude: /^node_modules$/,
            loader: 'file-loader?name=[name].[ext]'
        }, {
            test: /\.(png|jpg|gif)$/,
            exclude: /^node_modules$/,
            loader: 'url-loader?limit=8192&name=images/[hash:8].[name].[ext]',
            //注意後面那個limit的參數,當你圖片大小小於這個限制的時候,會自動啓用base64編碼圖
        }, {
            test: /\.jsx$/,
            exclude: /^node_modules$/,
            loaders: ['jsx', 'babel']
        }]
    },
    plugins: [
        new webpack.DefinePlugin({
            'process.env': {
                NODE_ENV: JSON.stringify('production') //定義生產環境
            }
        }),
        new HtmlWebpackPlugin({                    //根據模板插入css/js等生成最終HTML
            filename: '../index.html',             //生成的html存放路徑,相對於 path
            template: './src/template/index.html', //html模板路徑
            inject: 'body',
            hash: true,
        }),
        new ExtractTextPlugin('[name].css'),
        //提取出來的樣式和common.js會自動添加進發布模式的html文件中,原來的html沒有
        new webpack.optimize.CommonsChunkPlugin("common", "common.bundle.js"),
        new webpack.optimize.UglifyJsPlugin({
            output: {
                comments: false, // remove all comments (移除全部註釋)
            },
            compress: {          // 壓縮
                warnings: false
            }
        })
    ]
};

與前面講的第一大節相比,並沒有明顯區別,主要是加了一些將項目發佈到服務的線上流程。
其中:

(1)、entry
entry: {
    app: APP_FILE,
    common: [
        "react",
        'react-dom',
        'react-router',
        'redux',
        'react-redux',
        'redux-thunk',
        'immutable'
    ]
},

添加了 common 用來單獨打包出公共部分的 js代碼

(2)、plugins
plugins: [
    new ExtractTextPlugin('[name].css'),
    new webpack.optimize.CommonsChunkPlugin("common", "common.bundle.js"),
    new webpack.optimize.UglifyJsPlugin({
        output: {
            comments: false, // remove all comments (移除全部註釋)
        },
        compress: {          // 壓縮
            warnings: false
        }
    })
]

webpack.config.build.js 新添了幾個 plugins 。首先要清楚的一點是 webpack 是將一塊塊的依賴打包的一個文件裏頭的,
不論是 js 、scss、less、css、jsx 文件都會編譯成一塊塊的代碼打包到一個文件裏頭。那插件能夠將一塊塊的代碼看是壓縮或提取出來
ExtractTextPlugin 插件的做用就是將 css塊區域的代碼單獨提取出來的。
CommonsChunkPlugin 是用來提取公用的代碼塊。有兩個參數 common 是對應 entry 的字段,common.bundle.js是將
公共代碼輸出到 common.bundle.js 文件裏。
UglifyJsPlugin 是將打包後的代碼鏡像壓縮。

總結

lesson-1 主要是對 webpack 打包編譯的一些講解和梳理。

  • 第一節講了 webpack 的簡單工做原理, // webpack

  • 第二節講了 實際開發過程當中 支持瀏覽器自動刷新,對webpack進行相應的改造, // npm run hot

  • 第三節講了 將項目發佈到線上的一些實際打包的工做流程。 // npm run build

後面還有 lesson 來說解 React 配合 Redux、 Router 在實際項目中的應用和開發,喜歡的話能夠先 star 一下哦!!!
項目地址 github-react-lesson

相關文章
相關標籤/搜索