也談 webpack 及其開發模式

從模塊化談起

近年來,js開發涌現出了諸多模塊化解決方案,例如以在瀏覽器環境以外(服務端)構建 JavaScript 生態系統爲目標而產生的CommonJS規範,從CommonJS社區中獨立出來的AMD規範(異步模塊定義),還有國人制定的CMD規範等。隨着遵循AMD規範的RequireJS的流行,AMD規範在前端界已被普遍認同。後來,隨着npm社區的逐漸壯大,CommonJS也愈來愈受歡迎,因而產生了統一這兩種規範的需求,即但願提供一個先後端跨平臺的解決方案,也所以產生了UMD(通用模塊定義)規範。javascript

CommonJS定義的是模塊的同步加載,主要用於Node端;而遵循AMD規範的RequireJS則是異步加載,適用於瀏覽器端。requirejs是一種在線」編譯」 模塊的方案,至關於在頁面上加載一個AMD 解釋器,以便於覽器可以識別 define、exports、module,而這些東西就是用於模塊化的關鍵。css

1. CommonJS 同步式的requirehtml

Node端的模塊加載遵循 CommonJS規範,該規範的核心思想是容許模塊經過 require 方法來加載。前端

該規範首先加載所要依賴的其餘模塊,而後經過 exportsmodule.exports 來導出須要暴露的接口。但它的缺點也是顯而易見的,即一個文件一個文件的加載很容易發生阻塞。java

require("module");//find from node_modules
require("../file.js");
exports.something = function() {};
module.exports = something;

2. 使你的模塊兼容AMD規範node

//web.js
(function (root, factory) {
        //判斷define是否存在
    if (typeof define === 'function' && define.amd) {
        // 存在則使用AMD方式加載模塊
        define(['b'], factory);
    } else {
        // 不存在則使用瀏覽器全局變量暴露模塊 
        root.web = factory(root.b);
    }
}(this, function (b) {
    //use b in some fashion.

    // Just return a value to define the module export.
    // This example returns an object, but the module
    // can return a function as the exported value.
    return {};
}));

定義一個叫web.js的模塊,依賴於另外一個叫b的模塊。若是你不想支持瀏覽器全局路徑,那麼你能夠移除root並傳遞this參數在函數頂部。react

3. define.amd屬性jquery

爲了清晰的標識全局函數(爲瀏覽器加載script必須的)聽從AMD編程接口,任何全局函數應該有一個"amd"的屬性,它的值爲一個對象。webpack

關於RequireJS的使用不在本文範圍以內,所以不展開講解,有興趣的請移步個人另外一篇文章:git

詳解JavaScript模塊化開發:http://www.javashuo.com/article/p-tvzesnwv-ku.html

AMD與異步加載

由於CommonJS阻塞式的缺點,因此並不適合前端。因而有了AMD異步加載模塊的規範。

Asynchronous Module Definition 規範其實只有一個主要接口 define(id?, dependencies?, factory),它要在聲明模塊的時候指定全部的依賴 dependencies,而且還要當作形參傳到 factory 中,對於依賴的模塊提早執行,依賴前置。

由於瀏覽器端的需求和同步require的問題,因此社區引進了異步模塊加載的規範,即AMD規範。

define("module", ["dep1", "dep2"], function(d1, d2) {
  return someExportedValue;
});
require(["module", "../file"], function(module, file) { /* ... */ });

使你的模塊兼容於UMD規範:

//UMD,兼容AMD和CommonJS規範
(function (root, factory) {
  if (typeof exports === 'object') {
    // CommonJS
    module.exports = factory(require('b'));
  } else if (typeof define === 'function' && define.amd) {
    // AMD
    define(['b'], function (b) {
      return (root.returnExportsGlobal = factory(b));
    });
  } else {
    // 瀏覽器全局變量,root即window
    root.returnExportsGlobal = factory(root.b);
  }
}(this, function (b) {
  // 你的實際模塊
  return {};
}));

UMD規範實現的思路:

  • 首先判斷是否支持Node.js模塊格式,即exports對象是否存在。

  • 而後判斷是否支持AMD格式(require是否存在),存在則使用AMD方式加載

  • 若前兩個都不存在,則將模塊暴露到全局,Nodeglobal,瀏覽器即window。

例如,建立一個兼容UMD規範的jQuery插件:

// Uses CommonJS, AMD or browser globals to create a jQuery plugin.

(function (factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['jquery'], factory);
    } else if (typeof module === 'object' && module.exports) {
        // Node/CommonJS
        module.exports = function( root, jQuery ) {
            if ( jQuery === undefined ) {
                // require('jQuery') returns a factory that requires window to
                // build a jQuery instance, we normalize how we use modules
                // that require this pattern but the window provided is a noop
                // if it's defined (how jquery works)
                if ( typeof window !== 'undefined' ) {
                    jQuery = require('jquery');
                }
                else {
                    jQuery = require('jquery')(root);
                }
            }
            factory(jQuery);
            return jQuery;
        };
    } else {
        // Browser globals
        factory(jQuery);
    }
}(function ($) {
    $.fn.jqueryPlugin = function () { return true; };

CMD

Common Module Definition 規範和 AMD 很類似,儘可能保持簡單,並與 CommonJSNode.jsModules 規範保持了很大的兼容性。

define(function(require, exports, module) {
  var $ = require('jquery');
  var Spinning = require('./spinning');
  exports.doSomething = ...
  module.exports = ...
})

CMD規範地址:https://github.com/seajs/seajs/issues/242

ES6 module

ECMAScript6 內建的用法:

import "jquery";
export function doStuff() {}
module "localModule" {}

爲何只載入JavaScript文件?

爲何模塊化系統只幫助開發者處理JavaScript?然而還有其餘靜態資源須要被處理,好比:

stylesheets
images
webfonts
html for templating
其餘..
coffeescript ➞ javascript
less stylesheet ➞ css
jade ➞ html
i18n ➞ something
require("./style.css");
require("./style.less");
require("./template.jade");
require("./image.png");

由於上面這些動機,因此有了webpack

webpack

webpack是一款模塊封裝工具(module bundler,是打包工具,也是模塊加載工具,各類資源均可以當成模塊來處理),webpack 會將模塊與其餘相關聯的模塊,函數庫,其餘須要預編譯的文件等整合,編譯輸出此模塊的靜態資源文件。

// webpack.config.js `like=>`  gulpfile.js/gruntfile.js

module.exports = {
    entry: "./entry.js",
    output: {
        path: __dirname,
        filename: "bundle.js"
    },
    //module 對象用於添加loaders
    module: {
        //一個用於加載loader的數組
        loaders: [
            { test: /\.css$/, loader: "style!css" }
        ]
    }
};

module具備以下屬性:

  • test: 須要知足的條件A condition that must be met

  • exclude: 不須要知足的條件A condition that must not be met

  • include: 須要知足的條件A condition that must be met

  • loader: !用於分隔loaders

  • loaders: 一個loaders數組An array of loaders as string

"include" 一般被用於匹配目錄:

include: [
    path.resolve(__dirname, "app/src"),
    path.resolve(__dirname, "app/test")
  ],

簡單的說,webpack會把咱們經常使用的 .less, .scss, .jade, .jsx 等等文件編譯成純 js + 圖片(圖片有時也能夠被編譯成 base64 格式的 dataUrl)。

webpack的優點和特色:

  • 將依賴項分塊,按需加載

  • 儘量減小初始化載入的時間

  • 使每個靜態資源都可以做爲組件使用

  • 有能力整合其餘第三方函數庫爲模塊

  • 高度可配置化

  • 適合大型項目

webpack擁有更聰明的解析工具能夠處理幾乎全部的第三方函數庫。甚至容許在相依性設定上使用表達式,例如: require("./templates/" + name + ".jade"),這幾乎能處理大部分的模塊化標準(CommonJS, AMD)。

webpack經常使用命令

webpack 最基本的啓動webpack命令
webpack -w 提供watch方法,實時進行打包更新
webpack -p 對打包後的文件進行壓縮
webpack -d 提供SourceMaps,方便調試
webpack --colors 輸出結果帶彩色,好比:會用紅色顯示耗時較長的步驟
webpack --profile 輸出性能數據,能夠看到每一步的耗時
webpack --display-modules 默認狀況下 node_modules 下的模塊會被隱藏,加上這個參數能夠顯示這些被隱藏的模塊

在項目中使用webpack

首先在項目根目錄新建一個package.json或者經過$ npm init指令來產生:

接着經過npm指令安裝webpack

$ npm install webpack --save-dev

or => $ cnpm i webpack -g

單純的編譯指令

$ webpack <entry> <output>

配置對象內容

context:用來指明entry選項的基礎目錄(絕對路徑)。默認值爲process.cmd(),即webpack.config.js文件所在路徑

對象entry

定義了打包後的入口文件,能夠是數組(全部文件打包生成一個filename文件),對象或者字符串

{
    entry: {
        page1: "./page1",
        page2: ["./entry1", "./entry2"]
    },
    output: {
        // 在 output.filename 中使用 [name]或者[id],當使用多個entry points時
        filename: "[name].bundle.js",
        path: "dist/js/page",
        chunkFilename: "[id].bundle.js" //chunkFilename是非主入口的文件名
    }
}

該段代碼最終會生成一個page1.bundle.jspage2.bundle.js,並存放到 ./dist/js/page 文件夾下

chunkFilename是非主入口的文件名,當按需異步加載模塊的時候,這時生成的文件名是以chunkname配置的

  • output:該參數是個對象,定義了輸出文件的位置及名字:

  • path: 打包文件存放的絕對路徑

  • publicPath: 網站運行時的訪問路徑URL

  • filename:打包後的文件名

你可使用<name>=<filename> 的格式來替代 entry point 創建一個別名:

// webpack.config.js
module.exports = {
  output: {
    filename: "[name].bundle.js"
  }
}

接着執行以下命令:

$ webpack index=./entry.js
>> Output a file that is named index.bundle.js

對象output

表示欲輸出的路徑,其會被映射到設定檔中的 output.path 以及 output.filename

  • output.filename:指定每個在磁盤上輸出的文件名,不容許指定絕對路徑

  • output.path:輸出絕對路徑目錄(必須)

  • output.publicPath:指定在瀏覽器端引用的文件公開URL地址

對象resolve

webpack在構建包的時候會按目錄進行文件的查找,resolve屬性中的extensions數組可用於配置程序能夠自行補全哪些文件後綴。extensions 第一個是空字符串,對應不須要後綴的狀況。好比,爲了查找CoffeeScript文件,你的數組應當包含字符串".coffee"。使用extensions,在引入模塊的時候就不須要寫後綴,會自動補全

resolve: {
        extensions: ['', '.js', '.jsx','.es6','css','scss','png','jpg']
    },

resolve.alias 定義別名

alias:{
       'react-dom':path.join(nodeModulesPath,'/dist/react-dom'),
        'redux': path.join(nodeModulesPath,'dist/redux')
    },

對象externals

當咱們想在項目中require一些其餘的類庫或者API,而又不想讓這些類庫的源碼被構建到運行時文件中,
這在實際開發中頗有必要。此時咱們就能夠經過配置externals參數來解決這個問題:

externals: {
     "jquery": "jQuery"
 }

這樣咱們就能夠放心的在項目中使用這些API了:

var $ = require(「jquery」);

配置項詳情:https://webpack.github.io/docs/configuration.html

css樣式和圖片的加載

你能夠在你的js文件裏引入css文件和圖片,例如:

require('./bootstrap.css');
require('./style.less');
require('../../main.scss');

var img = document.createElement('img');
img.src = require('./myImg.png');

當你require了CSS(less或者其餘)文件,webpack會在頁面中插入一個內聯的<style>標籤去引入樣式。當你require圖片的時候,bundle文件會包含圖片的url,並經過require()返回圖片的url

固然,你須要在webpack.config.js裏作相應的配置:

// webpack.config.js
module.exports = {
  entry: './entry.js',//入口文件
  output: {
    path: './build', // 輸出js和圖片的目錄
    publicPath: 'http://mycdn.com/', // 用來生成圖片的地址
    filename: 'bundle.js'
  },
  module: {
    loaders: [
      { test: /\.less$/, loader: 'style-loader!css-loader!less-loader' }, // 用!去鏈式調用loader
      { test: /\.css$/, loader: 'style-loader!css-loader' },
      { test: /\.scss$/,loaders: ["style", "css", "sass"]}
      {test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'} // 內聯的base64的圖片地址,圖片要小於8k,直接的url的地址則不解析
    ]
  }
};

固然,你須要先經過npm包來安裝這些loader,webpack會經過test查找匹配文件,而後加載相應的loader。好比經過npm安裝sass loader

$ npm install sass-loader node-sass webpack --save-dev

實例一

新建一個content.js

// content.js
module.exports = "It works from content.js";

而後編輯 entry.js 加入 require

// File: entry.js
document.write(require("./content.js"));

打開瀏覽器,看到屏幕輸出:"It works from content.js";

Webpack 會給每一個模塊一個惟一的 ID 而後經過 ID 存取這些模塊,這些模塊都會被整合到 bundle.js 裏面。

webpack插件和使用方法介紹

1. CommonsChunkPlugin合併公共代碼

它用於提取多個入口文件的公共腳本部分,而後生成一個 公共文件來方便多頁面之間進行復用。如下是該插件的使用方法和webpack的具體用法介紹:

var webpack = require('webpack');
var path = require('path');
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js');

var config = {
    //Webpack 自己內置了一些經常使用的插件,還能夠經過 npm 安裝第三方插件。
    plugins: [commonsPlugin],
    //頁面入口文件配置
    entry: {
        index : './index.js'
    },
    //入口文件輸出配置
    output: {
        path: 'dist/js/page',
        filename: '[name].js'
    },
    //module 的做用是添加loaders
    module: {
        //加載器配置
        loaders: [
            //.css 文件使用 style-loader 和 css-loader 來處理
            { test: /\.css$/, loader: 'style-loader!css-loader' },//test屬性匹配css文件
            //.js 文件使用 jsx-loader 來編譯處理
            { test: /\.js$/, loader: 'jsx-loader?harmony' },
            //.scss 文件使用 style-loader、css-loader 和 sass-loader 來編譯處理
            { test: /\.scss$/, loader: 'style!css!sass?sourceMap'},
            //圖片文件使用 url-loader 來處理,小於8kb的直接轉爲base64
            { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'},
            {//處理字體
              test: /\.(woff2?|otf|eot|svg|ttf)$/i,
            },
            loader: 'style!css'//加載style和css loader
        ]
    },
    //其它解決方案配置
    resolve: {
        root: '/Users/trigkit4/webpack', //絕對路徑
        extensions: ['', '.js', '.json', '.scss'],//文件擴展名
        //模塊別名定義,方便後續直接引用別名
        alias: {
            a : './assets/a.js',  // require(「a」)便可引用該模塊
            b : './assets/b.js',
            c : './assets/c.js'
        }
    }
};

module.exports = config;

Webpack中將打包後的文件都稱之爲Chunk。這個插件能夠將多個打包後的資源中的公共部分打包成單獨的文件。這裏指定公共文件輸出爲common.js

Webpack 自己只能處理 JavaScript 模塊,若是要處理其餘類型的文件,就須要使用loader進行轉換。
不一樣模塊的加載是經過模塊加載器(webpack-loader)來統一管理的。

用來定義loader的串聯關係,」-loader」是能夠省略不寫的,多個loader之間用「!」鏈接起來,但全部的加載器都須要經過npm來加載。

2. UglifyJsPlugin壓縮js文件

//webpack.config.js

//...other webpack settings

plugins: [
    new Webpack.optimize.UglifyJsPlugin({
        compress: {
            warnings: false
        }
    })
]

3. Extract Text Plugin

你們都知道在 webpack CSS 是能夠被 require() 的,webpack 會自動生成一個 <style> 標籤並加入到 html<head>標籤 中。可是在發佈時,咱們可能只但願有一個被打包事後 css 文件。這時 Extract Text Plugin 就能幫助咱們完成這項任務。

//webpack.config.js
var ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
    module: {
        loaders: [
            { test: /\.css$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader") }
        ]
    },
    plugins: [
        new ExtractTextPlugin("app.bundle.css")
    ]
}

以上設置會輸出一個 app.bundle.css 的文件。

4. html-webpack-plugin

webpack中生成HTML的插件。

var HtmlWebpackPlugin = require('html-webpack-plugin')
var webpackConfig = {
  entry: 'index.js',
  output: {
    path: 'dist',
    filename: 'index_bundle.js'
  },
  plugins: [
    new HtmlWebpackPlugin({ //根據模板插入css/js等生成最終HTML
            favicon: './src/img/favicon.ico', //favicon路徑,經過webpack引入同時能夠生成hash值
            filename: './view/about.html', //生成的html存放路徑,相對於path
            template: './src/view/about.html', //html模板路徑
            inject: true, //js插入的位置,true/'head'/'body'/false
            hash: true, //爲靜態資源生成hash值
            chunks: ['vendors', 'about'],//須要引入的chunk,不配置就會引入全部頁面的資源
            minify: { //壓縮HTML文件    
                removeComments: true, //移除HTML中的註釋
                collapseWhitespace: false //刪除空白符與換行符
            }
        }),
  ]      
}

詳情:https://www.npmjs.com/package/html-webpack-plugin

5. ProvidePlugin

plugins: [
      
        new webpack.ProvidePlugin({
            React: 'react',
            ReactDOM: 'react-dom'
        })
    ]

ProvidePlugin 插件能夠定義一個共用的插件入口,之後的文件就不須要require('react')也能使用React了。

6. babel loader

Babel-loader可以將JSX/ES6 文件轉爲js文件

$ npm install babel-loader babel-core babel-preset-es2015 babel-preset-react --save-dev

配置:

module.exports = {
    entry: {}, //文件入口
    output: { }, //輸出出口
    module: {
        loaders: [ 
            {
                test:/\.js[x]?$/,
                loader: 'babel-loader',
                exclude:/node_modules/,
                query:{presets: ['es2015','react']}
            },
        ]
    },
    plugins: [ ],//編譯的時候所執行的插件數組
    devtool : "source-map" //調試模式
};

插件列表:http://webpack.github.io/docs/list-of-plugins.html

開發服務器安裝

Webpack開發服務器須要單獨安裝,一樣是經過npm進行:

$ sudo npm install -g webpack-dev-server

可使用webpack-dev-server直接啓動,也能夠增長參數來獲取更多的功能,

具體配置能夠參見官方文檔。在終端輸入:

$ webpack-dev-server

而後打開:http://localhost:8080/webpack-dev-server/index.html

webpack.config.js裏配置:

//使用webpack-dev-server,提升開發效率

module.exports={
    devServer: {
        contentBase: './',
        host: 'localhost',
        port: 9090, //默認8080
        inline: true, //能夠監控js變化
        hot: true, //熱啓動
    }
}

詳情:https://webpack.github.io/docs/webpack-dev-server.html

使用browser-sync實時刷新頁面

若是每次更改代碼都要從新執行webpack命令難免太過麻煩,因此這裏推薦使用browser-sync-webpack-plugin,能夠監聽代碼變化,實時刷新頁面。

安裝browser-sync-webpack-plugin

$ npm install --save-dev browser-sync-webpack-plugin
$ webpack --watch

webpack.config.js配置:

var BrowserSyncPlugin = require('browser-sync-webpack-plugin');
 
module.exports = {
  // ... 
  plugins: [
    new BrowserSyncPlugin({
      // browse to http://localhost:3000/ during development, 
      // ./public directory is being served 
      host: 'localhost',
      port: 3000,
      server: { baseDir: ['public'] } //根目錄就填'./'
    })
  ]
}

實例二:react+webpack+es6開發模式

相關文章
相關標籤/搜索