webpack學習手記

什麼是webpack

官方對webpack的定位是module bundler,也就是一個模塊打包器具。它將根據模塊的依賴關係進行靜態分析,然後將這些模塊按照指定的規則生成對應的靜態資源。

同類工具:

  • browserify
  • brunch
  • rollup

還有一些經常拿來與webpack作比較的比如 grunt,gulp。這些其實應該是 task Runners,是項目流構建工具。與打包工具本質上是不同的。一種側重於模塊載入打包,一種側重於項目流程式控制制管理。

什麼是模塊化?

在開發過程中將不同類型不同功能的資源分開處理,每一部分資源都看作是一個模塊,按照需要進行載入使用,這就是模塊化開發。

模塊化是提升網站可維護性、功能復用性的重要手段,它能實現積木式的搭建網頁。

<script>標籤

<script src="module.js"></script>n<script src="library.js"></script>n

script標籤也可以理解為是一種模塊化的方法。

弊端:

  • 全局作用域下容易造成變數衝突
  • 文件只能按照 <script> 的書寫順序進行載入
  • 開發人員必須主觀解決模塊和代碼庫的依賴關係
  • 在大型項目中各種資源難以管理,長期積累的問題導致代碼庫混亂不堪

CommonJS

該規範的核心思想是允許模塊通過 require 方法來同步載入所要依賴的其他模塊,然後通過 exports 或 module.exports 來導出需要暴露的介面。

require("../file.js");nexports.doStuff = function() {};nmodule.exports = someValue;n

優點:

  • 伺服器端模塊便於重用
  • NPM 中已經有將近20萬個可以使用模塊包
  • 簡單並容易使用

缺點:

  • 同步的模塊載入方式不適合在瀏覽器環境中,同步意味著阻塞載入,瀏覽器資源是非同步載入的
  • 不能非阻塞的並行載入多個模塊

AMD(CMD/UMD。。。)

Asynchronous Module Definition 規範其實只有一個主要介面 define(id?, dependencies?, factory),它要在聲明模塊的時候指定所有的依賴 dependencies,並且還要當做形參傳到 factory 中,對於依賴的模塊提前執行,依賴前置

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

優點:

  • 適合在瀏覽器環境中非同步載入模塊
  • 可以並行載入多個模塊

缺點:

  • 提高了開發成本,代碼的閱讀和書寫比較困難,模塊定義方式的語義不順暢
  • 不符合通用的模塊化思維方式,是一種妥協的實現

ES6 模塊

EcmaScript6 標準增加了 JavaScript 語言層面的模塊體系定義。ES6 模塊的設計思想,是盡量的靜態化,使得編譯時就能確定模塊的依賴關係,以及輸入和輸出的變數。CommonJS 和 AMD 模塊,都只能在運行時確定這些東西。

import "jquery";nexport function doStuff() {}nmodule "localModule" {}n

優點:

  • 容易進行靜態分析
  • 面向未來的 EcmaScript 標準

缺點:

  • 原生瀏覽器端還沒有實現該標準
  • 全新的命令字,新版的 Node.js才支持

Webpack 的特點

代碼拆分

Webpack 有兩種組織模塊依賴的方式,同步和非同步。非同步依賴作為分割點,形成一個新的塊。在優化了依賴樹後,每一個非同步區塊都作為一個文件被打包。

Loader

Webpack 本身只能處理原生的 JavaScript 模塊,但是 loader 轉換器可以將各種類型的資源轉換成 JavaScript 模塊。這樣,任何資源都可以成為 Webpack 可以處理的模塊。

智能解析

Webpack 有一個智能解析器,幾乎可以處理任何第三方庫,無論它們的模塊形式是 CommonJS、 AMD 還是普通的 JS 文件。甚至在載入依賴的時候,允許使用動態表達式 require("./templates/" + name + ".jade")。

插件系統

Webpack 還有一個功能豐富的插件系統。大多數內容功能都是基於這個插件系統運行的,還可以開發和使用開源的 Webpack 插件,來滿足各式各樣的需求。 快速運行

快速運行

Webpack 使用非同步 I/O和多級緩存提高運行效率,這使得 Webpack 能夠以令人難以置信的速度快速增量編譯。

webpack安裝

首先要安裝 Node.js。

用 npm 安裝 Webpack:

$ npm install webpack -gn

此時 Webpack 已經安裝到了全局環境下,可以通過命令行 webpack -h 試試。

通常我們會將 Webpack 安裝到項目的依賴中,這樣就可以使用項目本地版本的 Webpack。

# 進入項目目錄n# 確定已經有 package.json,沒有就通過 npm init 創建n# 安裝 webpack 依賴n$ npm install webpack --save-devn

註:接下來要講的webpack的loader及plugin,除去webpack自帶的plugin外,也需要npm安裝。

webpack配置文件

webpack簡單點來說就就是一個配置文件,所有的魔力都是在這一個文件中發生的。

這個配置文件主要分為幾個部分:

  • entry 入口文件

    讓webpack用哪個文件作為項目的入口
  • output 出口

    讓webpack把處理完成的文件放在哪裡
  • module 模塊

    要用什麼不同的模塊來處理各種類型的文件
  • plugins 模塊

    要用什麼不同的插件實現不同的效果

一個簡單的webpack配置文件內容大致如下:

var webpack = require(webpack);nvar path = require(path);nvar HtmlwebpackPlugin = require(html-webpack-plugin);nnmodule.exports = {n //項目的文件夾 可以直接用文件夾名稱 默認會找index.js 也可以確定是哪個文件名字n entry: ./index.js,n //輸出的文件名 合併以後的js會命名為bundle.jsn output: {n path: __dirname,n filename: ./dist/bundle.jsn },n //配置一些文件的loadern module: {n loaders: [n {test: /.css$/, loader: style!css}n ]n }n //添加我們的插件 會自動生成一個html文件n plugins: [n new HtmlwebpackPlugin({n title: Hello World appn })n ]n};n

在配置文件中,主要有幾個部分:

entry

配置打包文件的入口。 可以是直接指定某一文件,如:

entry: ./index.jsn

可以是一個數組,指定多個入口文件,最後將打包成一個文件,如:

entry: [n ./index.js,n ./main.jsn]n

亦可以是一組對象,每一項成員對應一個打包文件,成員的key將被用作chunk name,成員的值可以是string或者array:

entry: {n page1: ./page1.js,n page2: [./page2.js,./func.js]n},noutput: {n // Make sure to use [name] or [id] in output.filenamen // when using multiple entry pointsn filename: "[name].bundle.js",n chunkFilename: "[id].bundle.js"n}n

output

對打包生成的文件進行配置。

output: {n path: __dirname,n filename: ./dist/bundle.jsn }n

output中幾個常用的屬性:

filename: 打包後的文件名;

path: 文件打包生成的路徑;

publicPath: 發布路徑,用於<script>標籤中的引用路徑,例如:

config.js

output: {n path: "/home/proj/public/assets",n publicPath: "/assets/"n}n

index.html

<head>n <link href="/assets/spinner.gif"/>n</head>n

chunkFilename :非入口文件的命名規則。例如一些需要非同步載入進來的文件,這樣的文件沒有列在entry中,但卻需要打包出來。

require.ensure(["modules/tips.jsx"], function(require) {n var a = require("modules/tips.jsx");n // ...n}, tips);n

source-map

想要開啟source-map的話,在config中添加:

...ndevtool: eval-source-map,n...n

這樣出錯以後就會採用source-map的形式直接顯示你出錯代碼的位置。

module.loaders

Webpack 本身只能處理 JavaScript 模塊,如果要處理其他類型的文件,就需要使用 loader 進行轉換。

loader一般張這個樣子:

module.loaders: [n {n // "test" is commonly used to match the file extensionn test: /.jsx$/,nn // "include" is commonly used to match the directoriesn include: [n path.resolve(__dirname, "app/src"),n path.resolve(__dirname, "app/test")n ],nn // "exclude" should be used to exclude exceptionsn // try to prefer "include" when possiblenn // the "loader"n loader: "babel-loader"n }n]n

其中:

  • test: 文件匹配規則
  • exclude: 排出的文件
  • include: 被匹配的文件
  • loaders: 一個設置由哪種規則載入的loader數組
  • loader: 與loaders一樣,區別是用 「!」 把loaders拼接在了一起

一些常用的loader:

  • 載入css:

loader: "style!css"//style-loader css-loadern

  • 載入圖片:

loader: "url-loader"//url-loadern

  • 載入es6:

loader: babel-loadern

其他還有諸如less-loader、sass-loader、jsx-loader等等,可以按照具體需要進行配置。

使用preLoaders和postLoaders

也許你想在寫代碼的時候檢查自己的js是否符合jshint的規範,此時就可以使用preLoaders和postLoaders,它們和loader的配置方式相同,區別在於處理的順序不同,preLoaders顧名思義就是在loaders執行之前處理的,webpack的處理順序是preLoaders - loaders - postLoaders。

在config文件中配置

module: {n...n //和loaders一樣的語法,很簡單n perLoaders: [n {n test: /.jsx?$/,n include: APP_PATH,n loader: jshint-loadern }n ]n}nn...n//配置jshint的選項,支持es6的校驗njshint: {n "esnext": truen},n

更多loader

resolve

在這個欄位下可以進行一些其他載入處理的配置。

resolve.alias

alias設置資源的別名,它的作用是把用戶的一個請求重定向到另一個路徑,例如:

resolve.alias: {n "react": client/lib/react.min.js,n}n

當代碼中require(react)時,webpack就會去載入 client/lib/react.min.js ;

resolve.extensions

用來設置載入模塊的文件拓展名,比如我按下面的方式來設置:

resolve.extensions: [, .js, .jsx]n

當我require(app.jsx)時,只需寫成require(app),extensions已經告訴了webpack載入模塊時去載入.jsx文件。

externals

用來定義一些不希望被打包進去的但是卻是項目所依賴的文件或庫。

externals: {n react: React,n react-dom: ReactDOMn},n

plugins

在plugins里可以設置一些插件,來幫助我們更好的進行開發。

一些常用的plugin:

UglifyJsPlugin用來壓縮打包後的文件。

var uglifyJsPlugin = webpack.optimize.UglifyJsPlugin;nplugins: [n new uglifyJsPlugin({n compress: {n warnings: falsen }n })n]n

HotModuleReplacementPlugin用來實現熱更新。

new webpack.HotModuleReplacementPlugin()n

open-browser-webpack-plugin打包完成後打開瀏覽器。

var OpenBrowserPlugin = require(open-browser-webpack-plugin);nnew OpenBrowserPlugin({n url: http://localhost:8000n})n

當完成webpack打包時會呼起瀏覽器並打開localhost:8000標籤頁。

ProvidePlugin可以幫助你設置全局變數,用它把全局變數插入到所有代碼中

new webpack.ProvidePlugin({n $: "jquery",n jQuery: "jquery",n "window.jQuery": "jquery"n })n

這樣不需要引入jquery文件就可以直接使用 $、jQuery等。

CommonsChunkPlugin用來分離第三方庫。

需要在入口文件中配置:

entry: {n app: path.resolve(APP_PATH, index.js),n //添加要打包在vendors裡面的庫n vendors: [jquery, moment]n }n

然後添加plugin:

plugins: [n ...n //把入口文件裡面的數組打包成verdors.jsn new webpack.optimize.CommonsChunkPlugin(vendors, vendors.js),n ...n ]n

這樣就會把jquery和moment單獨打包成一個文件。

transfer-webpack-plugin把指定目錄下的資源copy到特定目錄。

ar TransferWebpackPlugin = require(transfer-webpack-plugin);n...nplugins: [n //把指定文件夾下的文件複製到指定的目錄n new TransferWebpackPlugin([n {from: www}n ], path.resolve(__dirname,"src"))n ]n

把 www 目錄下的文件複製到 src 目錄里。

更多plugin

webpack-dev-server

webpack server官網

webpack-dev-server可以建立一個開發伺服器,實時查看我們的開發代碼。 簡單的dev-server配置如下:

var webpack = require(webpack);nvar webpackDevServer = require(webpack-dev-server);n...nmodule.exports = {n entry : [n "webpack-dev-server/client?http://localhost:8000/",n "webpack/hot/dev-server",n "your entry file here",n ]n ...n devServer: {n historyApiFallback: true,n hot: true,n inline: true,n progress: true,n }n}n

命令行執行 webpack-dev-server --hot --inline 就可以開啟伺服器了。

默認埠是8000,如果想更細緻的進行server配置,可以這樣寫:

var webpack = require(webpack);nvar webpackDevServer = require(webpack-dev-server);nconfig = {n ...//webpack的其他配置n};nnvar compiler = webpack(config);nvar server = new webpackDevServer(compiler, {n historyApiFallback: true,n hot: true,n inline: true,n progress: true,n contentBase: config.output.path,n stats: {n colors: truen }n});nnserver.listen(8888, "localhost", function(err) {n if (err) {n console.log(err);n }n console.log("listening at localhost:8888...");n})n

訪問 http://localhost:8888 就能看到伺服器開啟了。

webpack + gulp

在gulp中使用webpack進行打包。

var gulp = require("gulp");nvar gutil = require("gulp-util");nvar webpack = require("webpack");nvar WebpackDevServer = require("webpack-dev-server");nngulp.task("webpack", function(callback) {n // run webpackn webpack({n // configurationn }, function(err, stats) {n if(err) throw new gutil.PluginError("webpack", err);n gutil.log("[webpack]", stats.toString({n // output optionsn }));n callback();n });n});n

webpack + express

使用webpack作為express的中間件。

var express = require(express);nvar webpack = require(webpack);nvar path = require(path);nvar webpackConfig = require(./webpack.config);nnnvar app = express();nn// webpack編譯器nvar compiler = webpack(webpackConfig);nn// webpack-dev-server中間件nvar devMiddleware = require(webpack-dev-middleware)(compiler, {n publicPath: webpackConfig.output.publicPath,n hot: true,n inline: true,n historyApiFallback: true,n stats: {n colors: truen }n});n//中間件熱載入nvar hotMiddleware = require(webpack-hot-middleware)(compiler);nnapp.use(devMiddleware);napp.use(hotMiddleware);n// 路由napp.get(/, function(req, res) {n res.send(地址欄輸入 localhost:8080/ + 你的頁面文件夾名稱 訪問對應頁面哦);n});napp.get(/:viewname?, function(req, res, next) {nn var viewname = req.params.viewname ?n req.params.viewname + .html :n index.html;nn var filepath = path.join(compiler.outputPath, viewname);nn // 使用webpack提供的outputFileSystemn compiler.outputFileSystem.readFile(filepath, function(err, result) {n if (err) {n // something errorn return next(err);n }n res.set(content-type, text/html);n res.send(result);n res.end();n });n});nnvar port = 8080;nmodule.exports = app.listen(port, function(err) {n if (err) {n // do somethingn return;n }nn console.log(Listening at http://localhost: + port + n)n})n

推薦閱讀:

前端入門之網頁性能優化(三)
如何在不刷新頁面的情況下改變URL

TAG:前端开发 | webpack | 前端入门 |