国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

webpack實戰(zhàn)

cyrils / 1014人閱讀

摘要:和類似的預(yù)處理器還有等。的用處非常多,包括給自動加前綴使用下一代語法等,目前越來越多的人開始用它,它很可能會成為預(yù)處理器的最終贏家。

webpack實戰(zhàn)
查看所有文檔頁面:全棧開發(fā),獲取更多信息。

快馬加鞭,加班加點,終于把這個文檔整理出來了,順便深入地學(xué)習(xí)一番,鞏固知識,就是太累人,影響睡眠時間和質(zhì)量。極客就是想要把事情做到極致,開始了就必須到達終點。

原文鏈接:webpack實戰(zhàn),原文廣告模態(tài)框遮擋,閱讀體驗不好,所以整理成本文,方便查找。

本章教你如何用 Webpack 去解決實際項目中常見的場景。

按照不同場景劃分成以下幾類:

使用新語言來開發(fā)項目:

使用 ES6 語言

使用 TypeScript 語言

使用 Flow 檢查器

使用 SCSS 語言

使用 PostCSS

使用新框架來開發(fā)項目:

使用 React 框架

使用 Vue 框架

使用 Angular2 框架

用 Webpack 構(gòu)建單頁應(yīng)用:

為單頁應(yīng)用生成 HTML

管理多個單頁應(yīng)用

用 Webpack 構(gòu)建不同運行環(huán)境的項目:

構(gòu)建同構(gòu)應(yīng)用

構(gòu)建 Electron 應(yīng)用

構(gòu)建 Npm 模塊

構(gòu)建離線應(yīng)用

Webpack 結(jié)合其它工具搭配使用,各取所長:

搭配 Npm Script

檢查代碼

通過 Node.js API 啟動 Webpack

使用 Webpack Dev Middleware

用 Webpack 加載特殊類型的資源:

加載圖片

加載SVG

加載 Source Map

使用 TypeScript 語言

由于本文不推薦使用TypeScript,ES6就足夠完成大部分任務(wù)。原文鏈接:使用 TypeScript 語言

使用 Angular2 框架

Angular2不在我的技術(shù)棧范圍,所以這一章不加入,有興趣的查看原文:使用 Angular2 框架

使用ES6語言

通常我們需要把采用 ES6 編寫的代碼轉(zhuǎn)換成目前已經(jīng)支持良好的 ES5 代碼,這包含2件事:

把新的 ES6 語法用 ES5 實現(xiàn),例如 ES6 的 class 語法用 ES5 的 prototype 實現(xiàn)。

給新的 API 注入 polyfill ,例如使用新的 fetch API 時注入對應(yīng)的 polyfill 后才能讓低端瀏覽器正常運行。

Babel

Babel 可以方便的完成以上2件事。

Babel 是一個 JavaScript 編譯器,能將 ES6 代碼轉(zhuǎn)為 ES5 代碼,讓你使用最新的語言特性而不用擔(dān)心兼容性問題,并且可以通過插件機制根據(jù)需求靈活的擴展。

在 Babel 執(zhí)行編譯的過程中,會從項目根目錄下的 .babelrc 文件讀取配置。.babelrc 是一個 JSON 格式的文件,內(nèi)容大致如下:

{
  "plugins": [
    [
      "transform-runtime",
      {
        "polyfill": false
      }
    ]
   ],
  "presets": [
    [
      "es2015",
      {
        "modules": false
      }
    ],
    "stage-2",
    "react"
  ]
}
Plugins

plugins 屬性告訴 Babel 要使用哪些插件,插件可以控制如何轉(zhuǎn)換代碼。

以上配置文件里的 transform-runtime 對應(yīng)的插件全名叫做 babel-plugin-transform-runtime,即在前面加上了 babel-plugin-,要讓 Babel 正常運行我們必須先安裝它:

npm i -D babel-plugin-transform-runtime

babel-plugin-transform-runtime 是 Babel 官方提供的一個插件,作用是減少冗余代碼。

Babel 在把 ES6 代碼轉(zhuǎn)換成 ES5 代碼時通常需要一些 ES5 寫的輔助函數(shù)來完成新語法的實現(xiàn),例如在轉(zhuǎn)換 class extent 語法時會在轉(zhuǎn)換后的 ES5 代碼里注入 _extent 輔助函數(shù)用于實現(xiàn)繼承:

function _extent(target) {
  for (var i = 1; i < arguments.length; i++) {
    var source = arguments[i];
    for (var key in source) {
      if (Object.prototype.hasOwnProperty.call(source, key)) {
        target[key] = source[key];
      }
    }
  }
  return target;
}

這會導(dǎo)致每個使用了 class extent 語法的文件都被注入重復(fù)的 _extent 輔助函數(shù)代碼,babel-plugin-transform-runtime 的作用在于不把輔助函數(shù)內(nèi)容注入到文件里,而是注入一條導(dǎo)入語句:

var _extent = require("babel-runtime/helpers/_extent");

這樣能減小 Babel 編譯出來的代碼的文件大小。

同時需要注意的是由于 babel-plugin-transform-runtime 注入了 require("babel-runtime/helpers/_extent") 語句到編譯后的代碼里,需要安裝 babel-runtime 依賴到你的項目后,代碼才能正常運行。 也就是說 babel-plugin-transform-runtimebabel-runtime 需要配套使用,使用了 babel-plugin-transform-runtime 后一定需要 babel-runtime

Presets

presets 屬性告訴 Babel 要轉(zhuǎn)換的源碼使用了哪些新的語法特性,一個 Presets 對一組新語法特性提供支持,多個 Presets 可以疊加。

Presets 其實是一組 Plugins 的集合,每一個 Plugin 完成一個新語法的轉(zhuǎn)換工作。Presets 是按照 ECMAScript 草案來組織的,通常可以分為以下三大類:

已經(jīng)被寫入 ECMAScript 標準里的特性,由于之前每年都有新特性被加入到標準里;

env 包含當(dāng)前所有 ECMAScript 標準里的最新特性。

被社區(qū)提出來的但還未被寫入 ECMAScript 標準里特性,這其中又分為以下四種:

stage0 只是一個美好激進的想法,有 Babel 插件實現(xiàn)了對這些特性的支持,但是不確定是否會被定為標準;

stage1 值得被納入標準的特性;

stage2 該特性規(guī)范已經(jīng)被起草,將會被納入標準里;

stage3 該特性規(guī)范已經(jīng)定稿,各大瀏覽器廠商和 Node.js 社區(qū)開始著手實現(xiàn);

stage4 在接下來的一年將會加入到標準里去。

為了支持一些特定應(yīng)用場景下的語法,和 ECMAScript 標準沒有關(guān)系,例如 babel-preset-react 是為了支持 React 開發(fā)中的 JSX 語法。

在實際應(yīng)用中,你需要根據(jù)項目源碼所使用的語法去安裝對應(yīng)的 Plugins 或 Presets。

接入 Babel

由于 Babel 所做的事情是轉(zhuǎn)換代碼,所以應(yīng)該通過 Loader 去接入 Babel,Webpack 配置如下:

module.exports = {
  module: {
    rules: [
      {
        test: /.js$/,
        use: ["babel-loader"],
      },
    ]
  },
  // 輸出 source-map 方便直接調(diào)試 ES6 源碼
  devtool: "source-map"
};

配置命中了項目目錄下所有的 JavaScript 文件,通過 babel-loader 去調(diào)用 Babel 完成轉(zhuǎn)換工作。 在重新執(zhí)行構(gòu)建前,需要先安裝新引入的依賴:

# Webpack 接入 Babel 必須依賴的模塊
npm i -D babel-core babel-loader 
# 根據(jù)你的需求選擇不同的 Plugins 或 Presets
npm i -D babel-preset-env
使用SCSS語言

SCSS 可以讓你用更靈活的方式寫 CSS。 它是一種 CSS 預(yù)處理器,語法和 CSS 相似,但加入了變量、邏輯等編程元素,代碼類似這樣:

$blue: #1875e7; 

div {
  color: $blue;
}

SCSS 又叫 SASS,區(qū)別在于 SASS 語法類似 Ruby,而 SCSS 語法類似 CSS,對于熟悉 CSS 的前端工程師來說會更喜歡 SCSS。

采用 SCSS 去寫 CSS 的好處在于可以方便地管理代碼,抽離公共的部分,通過邏輯寫出更靈活的代碼。 和 SCSS 類似的 CSS 預(yù)處理器還有 LESS 等。

使用 SCSS 可以提升編碼效率,但是必須把 SCSS 源代碼編譯成可以直接在瀏覽器環(huán)境下運行的 CSS 代碼。

node-sass 核心模塊是由 C++ 編寫,再用 Node.js 封裝了一層,以供給其它 Node.js 調(diào)用。 node-sass 還支持通過命令行調(diào)用,先安裝它到全局:

npm i -g node-sass

再執(zhí)行編譯命令:

# 把 main.scss 源文件編譯成 main.css
node-sass main.scss main.css
    

你就能在源碼同目錄下看到編譯后的 main.css 文件。

接入 Webpack

Webpack 接入 sass-loader 相關(guān)配置如下:

module.exports = {
  module: {
    rules: [
      {
        // 增加對 SCSS 文件的支持
        test: /.scss/,
        // SCSS 文件的處理順序為先 sass-loader 再 css-loader 再 style-loader
        use: ["style-loader", "css-loader", "sass-loader"],
      },
    ]
  },
};

以上配置通過正則 /.scss/ 匹配所有以 .scss 為后綴的 SCSS 文件,再分別使用3個 Loader 去處理。具體處理流程如下:

通過 sass-loader 把 SCSS 源碼轉(zhuǎn)換為 CSS 代碼,再把 CSS 代碼交給 css-loader 去處理。

css-loader 會找出 CSS 代碼中的 @importurl() 這樣的導(dǎo)入語句,告訴 Webpack 依賴這些資源。同時還支持 CSS Modules、壓縮 CSS 等功能。處理完后再把結(jié)果交給 style-loader 去處理。

style-loader 會把 CSS 代碼轉(zhuǎn)換成字符串后,注入到 JavaScript 代碼中去,通過 JavaScript 去給 DOM 增加樣式。如果你想把 CSS 代碼提取到一個多帶帶的文件而不是和 JavaScript 混在一起,可以使用1-5 使用Plugin 中介紹過的 ExtractTextPlugin。

由于接入 sass-loader,項目需要安裝這些新的依賴:

# 安裝 Webpack Loader 依賴
npm i -D  sass-loader css-loader style-loader
# sass-loader 依賴 node-sass
npm i -D node-sass
    
使用Flow檢查器

Flow 是一個 Facebook 開源的 JavaScript 靜態(tài)類型檢測器,它是 JavaScript 語言的超集。

你所需要做的就是在需要的地方加上類型檢查,例如在兩個由不同人開發(fā)的模塊對接的接口出加上靜態(tài)類型檢查,能在編譯階段就指出部分模塊使用不當(dāng)?shù)膯栴}。 同時 Flow 也能通過類型推斷檢查出 JavaScript 代碼中潛在的 Bug。

Flow 使用效果如下:

// @flow

// 靜態(tài)類型檢查
function square1(n: number): number {
  return n * n;
}
square1("2"); // Error: square1 需要傳入 number 作為參數(shù)

// 類型推斷檢查
function square2(n) {
  return n * n; // Error: 傳入的 string 類型不能做乘法運算
}
square2("2");


需要注意的時代碼中的第一行 // @flow 告訴 Flow 檢查器這個文件需要被檢查。
使用 Flow

Flow 檢測器由高性能跨平臺的 OCaml 語言編寫,它的可執(zhí)行文件可以通過:

npm i -D flow-bin

安裝,安裝完成后通過先配置 Npm Script:

"scripts": {
   "flow": "flow"
}

再通過 npm run flow 去調(diào)用 Flow 執(zhí)行代碼檢查。

除此之外你還可以通過:

npm i -g flow-bin

把 Flow 安裝到全局后,再直接通過 flow 命令去執(zhí)行代碼檢查。

安裝成功后,在項目根目錄下執(zhí)行 Flow 后,F(xiàn)low 會遍歷出所有需要檢查的文件并對其進行檢查,輸出錯誤結(jié)果到控制臺。

采用了 Flow 靜態(tài)類型語法的 JavaScript 是無法直接在目前已有的 JavaScript 引擎中運行的,要讓代碼可以運行需要把這些靜態(tài)類型語法去掉。

// 采用 Flow 的源代碼
function foo(one: any, two: number, three?): string {}

// 去掉靜態(tài)類型語法后輸出代碼
function foo(one, two, three) {}

有兩種方式可以做到這點:

flow-remove-types 可多帶帶使用,速度快。

babel-preset-flow 與 Babel 集成。

集成 Webpack

由于使用了 Flow 項目一般都會使用 ES6 語法,所以把 Flow 集成到使用 Webpack 構(gòu)建的項目里最方便的方法是借助 Babel。

安裝 npm i -D babel-preset-flow 依賴到項目。

修改 .babelrc 配置文件,加入 Flow Preset:

"presets": [
...[],
"flow"
]

往源碼里加入靜態(tài)類型后重新構(gòu)建項目,你會發(fā)現(xiàn)采用了 Flow 的源碼還是能正常在瀏覽器中運行。

要明確構(gòu)建的目的只是為了去除源碼中的 Flow 靜態(tài)類型語法,而代碼檢查和構(gòu)建無關(guān)。 許多編輯器已經(jīng)整合 Flow,可以實時在代碼中高亮指出 Flow 檢查出的問題。
使用PostCSS

PostCSS 是一個 CSS 處理工具,和 SCSS 不同的地方在于它通過插件機制可以靈活的擴展其支持的特性,而不是像 SCSS 那樣語法是固定的。 PostCSS 的用處非常多,包括給 CSS 自動加前綴、使用下一代 CSS 語法等,目前越來越多的人開始用它,它很可能會成為 CSS 預(yù)處理器的最終贏家。

PostCSS 和 CSS 的關(guān)系就像 Babel 和 JavaScript 的關(guān)系,它們解除了語法上的禁錮,通過插件機制來擴展語言本身,用工程化手段給語言帶來了更多的可能性。

PostCSS 和 SCSS 的關(guān)系就像 Babel 和 TypeScript 的關(guān)系,PostCSS 更加靈活、可擴張性強,而 SCSS 內(nèi)置了大量功能而不能擴展。

給 CSS 自動加前綴,增加各瀏覽器的兼容性:

/*輸入*/
h1 {
  display: flex;
}

/*輸出*/
h1 {
  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
}

使用下一代 CSS 語法:

/*輸入*/
:root {
  --red: #d33;
}

h1 {
  color: var(--red);
}


/*輸出*/
h1 { 
  color: #d33;
}

PostCSS 全部采用 JavaScript 編寫,運行在 Node.js 之上,即提供了給 JavaScript 代碼調(diào)用的模塊,也提供了可執(zhí)行的文件。

在 PostCSS 啟動時,會從目錄下的 postcss.config.js 文件中讀取所需配置,所以需要新建該文件,文件內(nèi)容大致如下:

module.exports = {
  plugins: [
    // 需要使用的插件列表
    require("postcss-cssnext")
  ]
}

其中的 postcss-cssnext 插件可以讓你使用下一代 CSS 語法編寫代碼,再通過 PostCSS 轉(zhuǎn)換成目前的瀏覽器可識別的 CSS,并且該插件還包含給 CSS 自動加前綴的功能。

目前 Chrome 等現(xiàn)代瀏覽器已經(jīng)能完全支持 cssnext 中的所有語法,也就是說按照 cssnext 語法寫的 CSS 在不經(jīng)過轉(zhuǎn)換的情況下也能在瀏覽器中直接運行。
接入 Webpack

雖然使用 PostCSS 后文件后綴還是 .css 但這些文件必須先交給 postcss-loader 處理一遍后再交給 css-loader

接入 PostCSS 相關(guān)的 Webpack 配置如下:

module.exports = {
  module: {
    rules: [
      {
        // 使用 PostCSS 處理 CSS 文件
        test: /.css/,
        use: ["style-loader", "css-loader", "postcss-loader"],
      },
    ]
  },
};

接入 PostCSS 給項目帶來了新的依賴需要安裝,如下:

# 安裝 Webpack Loader 依賴
npm i -D postcss-loader css-loader style-loader
# 根據(jù)你使用的特性安裝對應(yīng)的 PostCSS 插件依賴
npm i -D postcss-cssnext
    
使用React框架 React 語法特征

使用了 React 項目的代碼特征有 JSX 和 Class 語法,例如:

class Button extends Component {
  render() {
    return 

Hello,Webpack

} }
在使用了 React 的項目里 JSX 和 Class 語法并不是必須的,但使用新語法寫出的代碼看上去更優(yōu)雅。

其中 JSX 語法是無法在任何現(xiàn)有的 JavaScript 引擎中運行的,所以在構(gòu)建過程中需要把源碼轉(zhuǎn)換成可以運行的代碼,例如:

// 原 JSX 語法代碼
return 

Hello,Webpack

// 被轉(zhuǎn)換成正常的 JavaScript 代碼 return React.createElement("h1", null, "Hello,Webpack")
React 與 Babel

要在使用 Babel 的項目中接入 React 框架是很簡單的,只需要加入 React 所依賴的 Presets babel-preset-react

通過以下命令:

# 安裝 React 基礎(chǔ)依賴
npm i -D react react-dom
# 安裝 babel 完成語法轉(zhuǎn)換所需依賴
npm i -D babel-preset-react

安裝新的依賴后,再修改 .babelrc 配置文件加入 React Presets

"presets": [
    "react"
],

就完成了一切準備工作。

再修改 main.js 文件如下:

import * as React from "react";
import { Component } from "react";
import { render } from "react-dom";

class Button extends Component {
  render() {
    return 

Hello,Webpack

} } render(

重新執(zhí)行構(gòu)建打開網(wǎng)頁你將會發(fā)現(xiàn)由 React 渲染出來的 Hello,Webpack

React 與 TypeScript

TypeScript 相比于 Babel 的優(yōu)點在于它原生支持 JSX 語法,你不需要重新安裝新的依賴,只需修改一行配置。 但 TypeScript 的不同在于:

使用了 JSX 語法的文件后綴必須是 tsx

由于 React 不是采用 TypeScript 編寫的,需要安裝 reactreact-dom 對應(yīng)的 TypeScript 接口描述模塊 @types/react@types/react-dom 后才能通過編譯。

修改 TypeScript 編譯器配置文件 tsconfig.json 增加對 JSX 語法的支持,如下:

{
  "compilerOptions": {
    "jsx": "react" // 開啟 jsx ,支持 React
  }
}

由于 main.js 文件中存在 JSX 語法,再把 main.js 文件重命名為 main.tsx,同時修改文件內(nèi)容為在上面 React 與 Babel 里所采用的 React 代碼。 同時為了讓 Webpack 對項目里的 tstsx 原文件都采用 awesome-typescript-loader 去轉(zhuǎn)換, 需要注意的是 Webpack Loader 配置的 test 選項需要匹配到 tsx 類型的文件,并且 extensions 中也要加上 .tsx,配置如下:

module.exports = {
  // TS 執(zhí)行入口文件
  entry: "./main",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "./dist"),
  },
  resolve: {
    // 先嘗試 ts,tsx 后綴的 TypeScript 源碼文件 
    extensions: [".ts", ".tsx", ".js",] 
  },
  module: {
    rules: [
      {
        // 同時匹配 ts,tsx 后綴的 TypeScript 源碼文件 
        test: /.tsx?$/,
        loader: "awesome-typescript-loader"
      }
    ]
  },
  devtool: "source-map",// 輸出 Source Map 方便在瀏覽器里調(diào)試 TypeScript 代碼
};

通過npm i react react-dom @types/react @types/react-dom安裝新的依賴后重啟構(gòu)建,重新打開網(wǎng)頁你將會發(fā)現(xiàn)由 React 渲染出來的 Hello,Webpack

使用Vue框架

Vue是一個漸進式的 MVVM 框架,相比于 React、Angular 它更靈活輕量。 它不會強制性地內(nèi)置一些功能和語法,你可以根據(jù)自己的需要一點點地添加功能。 雖然采用 Vue 的項目能用可直接運行在瀏覽器環(huán)境里的代碼編寫,但為了方便編碼大多數(shù)項目都會采用 Vue 官方的單文件組件的寫法去編寫項目。

Vue 的單文件組件通過一個類似 HTML 文件的 .vue 文件就能描述清楚一個組件所需的模版、樣式、邏輯。

main.js 入口文件:

import Vue from "vue"
import App from "./App.vue"

new Vue({
  el: "#app",
  render: h => h(App)
});

入口文件創(chuàng)建一個 Vue 的根實例,在 ID 為 app 的 DOM 節(jié)點上渲染出上面定義的 App 組件。

接入 Webpack

目前最成熟和流行的開發(fā) Vue 項目的方式是采用 ES6 加 Babel 轉(zhuǎn)換,這和基本的采用 ES6 開發(fā)的項目很相似,差別在于要解析 .vue 格式的單文件組件。 好在 Vue 官方提供了對應(yīng)的 vue-loader 可以非常方便的完成單文件組件的轉(zhuǎn)換。

修改 Webpack 相關(guān)配置如下:

module: {
  rules: [
    {
      test: /.vue$/,
      use: ["vue-loader"],
    },
  ]
}

安裝新引入的依賴:

# Vue 框架運行需要的庫
npm i -S vue
# 構(gòu)建所需的依賴
npm i -D vue-loader css-loader vue-template-compiler

在這些依賴中,它們的作用分別是:

vue-loader:解析和轉(zhuǎn)換 .vue 文件,提取出其中的邏輯代碼 script、樣式代碼 style、以及 HTML 模版 template,再分別把它們交給對應(yīng)的 Loader 去處理。

css-loader:加載由 vue-loader 提取出的 CSS 代碼。

vue-template-compiler:把 vue-loader 提取出的 HTML 模版編譯成對應(yīng)的可執(zhí)行的 JavaScript 代碼,這和 React 中的 JSX 語法被編譯成 JavaScript 代碼類似。預(yù)先編譯好 HTML 模版相對于在瀏覽器中再去編譯 HTML 模版的好處在于性能更好。

使用 TypeScript 編寫 Vue 應(yīng)用

從 Vue 2.5.0+ 版本開始,提供了對 TypeScript 的良好支持,使用 TypeScript 編寫 Vue 是一個很好的選擇,因為 TypeScript 能檢查出一些潛在的錯誤。

新增 tsconfig.json 配置文件,內(nèi)容如下:

{
  "compilerOptions": {
    // 構(gòu)建出 ES5 版本的 JavaScript,與 Vue 的瀏覽器支持保持一致
    "target": "es5",
    // 開啟嚴格模式,這可以對 `this` 上的數(shù)據(jù)屬性進行更嚴格的推斷
    "strict": true,
    // TypeScript 編譯器輸出的 JavaScript 采用 es2015 模塊化,使 Tree Shaking 生效
    "module": "es2015",
    "moduleResolution": "node"
  }
}

修改 App.vue 腳本部分內(nèi)容如下:



注意 script 標簽中的 lang="ts" 是為了指明代碼的語法是 TypeScript。

修改 main.ts 執(zhí)行入口文件為如下:

import Vue from "vue"
import App from "./App.vue"

new Vue({
  el: "#app",
  render: h => h(App)
});

由于 TypeScript 不認識 .vue 結(jié)尾的文件,為了讓其支持 import App from "./App.vue" 導(dǎo)入語句,還需要以下文件 vue-shims.d.ts 去定義 .vue 的類型:

// 告訴 TypeScript 編譯器 .vue 文件其實是一個 Vue  
declare module "*.vue" {
  import Vue from "vue";
  export default Vue;
}

Webpack 配置需要修改兩個地方,如下:

const path = require("path");

module.exports = {
  resolve: {
    // 增加對 TypeScript 的 .ts 和 .vue 文件的支持
    extensions: [".ts", ".js", ".vue", ".json"],
  },
  module: {
    rules: [
      // 加載 .ts 文件
      {
        test: /.ts$/,
        loader: "ts-loader",
        exclude: /node_modules/,
        options: {
          // 讓 tsc 把 vue 文件當(dāng)成一個 TypeScript 模塊去處理,以解決 moudle not found 的問題,tsc 本身不會處理 .vue 結(jié)尾的文件
          appendTsSuffixTo: [/.vue$/],
        }
      },
    ]
  },
};

除此之外還需要安裝新引入的依賴:npm i -D ts-loader typescript

為單頁應(yīng)用生成HTML 引入問題

在使用 React 框架中,是用最簡單的 Hello,Webpack 作為例子讓大家理解, 這個例子里因為只輸出了一個 bundle.js 文件,所以手寫了一個 index.html 文件去引入這個 bundle.js,才能讓應(yīng)用在瀏覽器中運行起來。

在實際項目中遠比這復(fù)雜,一個頁面常常有很多資源要加載。接下來舉一個實戰(zhàn)中的例子,要求如下:

項目采用 ES6 語言加 React 框架。

給頁面加入 Google Analytics,這部分代碼需要內(nèi)嵌進 HEAD 標簽里去。

給頁面加入 Disqus 用戶評論,這部分代碼需要異步加載以提升首屏加載速度。

壓縮和分離 JavaScript 和 CSS 代碼,提升加載速度。

在開始前先來看看該應(yīng)用最終發(fā)布到線上的代碼。

可以看到部分代碼被內(nèi)嵌進了 HTML 的 HEAD 標簽中,部分文件的文件名稱被打上根據(jù)文件內(nèi)容算出的 Hash 值,并且加載這些文件的 URL 地址也被正常的注入到了 HTML 中。

解決方案

推薦一個用于方便地解決以上問題的 Webpack 插件 web-webpack-plugin。 該插件已經(jīng)被社區(qū)上許多人使用和驗證,解決了大家的痛點獲得了很多好評,下面具體介紹如何用它來解決上面的問題。

首先,修改 Webpack 配置。

以上配置中,大多數(shù)都是按照前面已經(jīng)講過的內(nèi)容增加的配置,例如:

增加對 CSS 文件的支持,提取出 Chunk 中的 CSS 代碼到多帶帶的文件中,壓縮 CSS 文件;

定義 NODE_ENV 環(huán)境變量為 production,以去除源碼中只有開發(fā)時才需要的部分;

給輸出的文件名稱加上 Hash 值;

壓縮輸出的 JavaScript 代碼。

但最核心的部分在于 plugins 里的:

new WebPlugin({
  template: "./template.html", // HTML 模版文件所在的文件路徑
  filename: "index.html" // 輸出的 HTML 的文件名稱
})

其中 template: "./template.html" 所指的模版文件 template.html 的內(nèi)容是:


  
  
  
  
  
  
  


該文件描述了哪些資源需要被以何種方式加入到輸出的 HTML 文件中。

為例,按照正常引入 CSS 文件一樣的語法來引入 Webpack 生產(chǎn)的代碼。href 屬性中的 app?_inline 可以分為兩部分,前面的 app 表示 CSS 代碼來自名叫 app 的 Chunk 中,后面的 _inline 表示這些代碼需要被內(nèi)嵌到這個標簽所在的位置。

同樣的 表示 JavaScript 代碼來自相對于當(dāng)前模版文件 template.html 的本地文件 ./google_analytics.js, 而且文件中的 JavaScript 代碼也需要被內(nèi)嵌到這個標簽所在的位置。

也就是說資源鏈接 URL 字符串里問號前面的部分表示資源內(nèi)容來自哪里,后面的 querystring 表示這些資源注入的方式。

除了 _inline 表示內(nèi)嵌外,還支持以下屬性:

_dist 只有在生產(chǎn)環(huán)境下才引入該資源;

_dev 只有在開發(fā)環(huán)境下才引入該資源;

_ie 只有IE瀏覽器才需要引入的資源,通過 [if IE]>resource 注釋實現(xiàn)。

這些屬性之間可以搭配使用,互不沖突。例如 app?_inline&_dist 表示只在生產(chǎn)環(huán)境下才引入該資源,并且需要內(nèi)嵌到 HTML 里去。

WebPlugin 插件還支持一些其它更高級的用法,詳情可以訪問該項目主頁閱讀文檔。

管理多個單頁應(yīng)用 引入問題

在開始前先來看看該應(yīng)用最終發(fā)布到線上的代碼。














構(gòu)建出的目錄結(jié)構(gòu)為:

dist
├── common_029086ff.js
├── common_7cc98ad0.css
├── index.html
├── index_04c08fbf.css
├── index_b3d3761c.js
├── login.html
├── login_0a3feca9.js
└── login_e31e214b.css

如果按照上節(jié)的思路,可能需要為每個單頁應(yīng)用配置一段如下代碼:

new WebPlugin({
  template: "./template.html", // HTML 模版文件所在的文件路徑
  filename: "login.html" // 輸出的 HTML 的文件名稱
})

并且把頁面對應(yīng)的入口加入到 enrty 配置項中,就像這樣:

entry: {
  index: "./pages/index/index.js",// 頁面 index.html 的入口文件
  login: "./pages/login/index.js",// 頁面 login.html 的入口文件
}

當(dāng)有新頁面加入時就需要修改 Webpack 配置文件,新插入一段以上代碼,這會導(dǎo)致構(gòu)建代碼難以維護而且易錯。

解決方案

項目源碼目錄結(jié)構(gòu)如下:

├── pages
│   ├── index
│   │   ├── index.css // 該頁面多帶帶需要的 CSS 樣式
│   │   └── index.js // 該頁面的入口文件
│   └── login
│       ├── index.css
│       └── index.js
├── common.css // 所有頁面都需要的公共 CSS 樣式
├── google_analytics.js
├── template.html
└── webpack.config.js

從目錄結(jié)構(gòu)中可以看成出下幾點要求:

所有單頁應(yīng)用的代碼都需要放到一個目錄下,例如都放在 pages 目錄下;

一個單頁應(yīng)用一個多帶帶的文件夾,例如最后生成的 index.html 相關(guān)的代碼都在 index 目錄下,login.html 同理;

每個單頁應(yīng)用的目錄下都有一個 index.js 文件作為入口執(zhí)行文件。

雖然 AutoWebPlugin 強制性的規(guī)定了項目部分的目錄結(jié)構(gòu),但從實戰(zhàn)經(jīng)驗來看這是一種優(yōu)雅的目錄規(guī)范,合理的拆分了代碼,又能讓新人快速的看懂項目結(jié)構(gòu),也方便日后的維護。

Webpack 配置文件修改如下:

See the Pen webpack管理多個單頁應(yīng)用 by whjin (@whjin) on CodePen.


由于這個模版文件被當(dāng)作項目中所有單頁應(yīng)用的模版,就不能再像上一節(jié)中直接寫 Chunk 的名稱去引入資源,因為需要被注入到當(dāng)前頁面的 Chunk 名稱是不定的,每個單頁應(yīng)用都會有自己的名稱。 的作用在于保證該頁面所依賴的資源都會被注入到生成的 HTML 模版里去。

web-webpack-plugin 能分析出每個頁面依賴哪些資源,例如對于 login.html 來說,插件可以確定該頁面依賴以下資源:

所有頁面都依賴的公共 CSS 代碼 common.css

所有頁面都依賴的公共 JavaScrip 代碼 common.js

只有這個頁面依賴的 CSS 代碼 login.css

只有這個頁面依賴的 JavaScrip 代碼 login.css

由于模版文件 template.html 里沒有指出引入這些依賴資源的 HTML 語句,插件會自動將沒有手動導(dǎo)入但頁面依賴的資源按照不同類型注入到 所在的位置。

CSS 類型的文件注入到 所在的位置,如果 不存在就注入到 HTML HEAD 標簽的最后;

JavaScrip 類型的文件注入到 所在的位置,如果 不存在就注入到 HTML BODY 標簽的最后。

如果后續(xù)有新的頁面需要開發(fā),只需要在 pages 目錄下新建一個目錄,目錄名稱取為輸出 HTML 文件的名稱,目錄下放這個頁面相關(guān)的代碼即可,無需改動構(gòu)建代碼。

由于 AutoWebPlugin 是間接的通過上一節(jié)提到的 WebPlugin 實現(xiàn)的,WebPlugin 支持的功能 AutoWebPlugin 都支持。

構(gòu)建同構(gòu)應(yīng)用

同構(gòu)應(yīng)用是指寫一份代碼但可同時在瀏覽器和服務(wù)器中運行的應(yīng)用。

認識同構(gòu)應(yīng)用

現(xiàn)在大多數(shù)單頁應(yīng)用的視圖都是通過 JavaScript 代碼在瀏覽器端渲染出來的,但在瀏覽器端渲染的壞處有:

搜索引擎無法收錄你的網(wǎng)頁,因為展示出的數(shù)據(jù)都是在瀏覽器端異步渲染出來的,大部分爬蟲無法獲取到這些數(shù)據(jù)。

對于復(fù)雜的單頁應(yīng)用,渲染過程計算量大,對低端移動設(shè)備來說可能會有性能問題,用戶能明顯感知到首屏的渲染延遲。

為了解決以上問題,有人提出能否將原本只運行在瀏覽器中的 JavaScript 渲染代碼也在服務(wù)器端運行,在服務(wù)器端渲染出帶內(nèi)容的 HTML 后再返回。 這樣就能讓搜索引擎爬蟲直接抓取到帶數(shù)據(jù)的 HTML,同時也能降低首屏渲染時間。 由于 Node.js 的流行和成熟,以及虛擬 DOM 提出與實現(xiàn),使這個假設(shè)成為可能。

實際上現(xiàn)在主流的前端框架都支持同構(gòu),包括 React、Vue2、Angular2,其中最先支持也是最成熟的同構(gòu)方案是 React。 由于 React 使用者更多,它們之間又很相似,本節(jié)只介紹如何用 Webpack 構(gòu)建 React 同構(gòu)應(yīng)用。

同構(gòu)應(yīng)用運行原理的核心在于虛擬 DOM,虛擬 DOM 的意思是不直接操作 DOM 而是通過 JavaScript Object 去描述原本的 DOM 結(jié)構(gòu)。 在需要更新 DOM 時不直接操作 DOM 樹,而是通過更新 JavaScript Object 后再映射成 DOM 操作。

虛擬 DOM 的優(yōu)點在于:

因為操作 DOM 樹是高耗時的操作,盡量減少 DOM 樹操作能優(yōu)化網(wǎng)頁性能。而 DOM Diff 算法能找出2個不同 Object 的最小差異,得出最小 DOM 操作;

虛擬 DOM 的在渲染的時候不僅僅可以通過操作 DOM 樹來表示出結(jié)果,也能有其它的表示方式,例如把虛擬 DOM 渲染成字符串(服務(wù)器端渲染),或者渲染成手機 App 原生的 UI 組件( React Native)。

以 React 為例,核心模塊 react 負責(zé)管理 React 組件的生命周期,而具體的渲染工作可以交給 react-dom 模塊來負責(zé)。

react-dom 在渲染虛擬 DOM 樹時有2中方式可選:

通過 render() 函數(shù)去操作瀏覽器 DOM 樹來展示出結(jié)果。

通過 renderToString() 計算出表示虛擬 DOM 的 HTML 形式的字符串。

構(gòu)建同構(gòu)應(yīng)用的最終目的是從一份項目源碼中構(gòu)建出2份 JavaScript 代碼,一份用于在瀏覽器端運行,一份用于在 Node.js 環(huán)境中運行渲染出 HTML。 其中用于在 Node.js 環(huán)境中運行的 JavaScript 代碼需要注意以下幾點:

不能包含瀏覽器環(huán)境提供的 API,例如使用 document 進行 DOM 操作,因為 Node.js 不支持這些 API;

不能包含 CSS 代碼,因為服務(wù)端渲染的目的是渲染出 HTML 內(nèi)容,渲染出 CSS 代碼會增加額外的計算量,影響服務(wù)端渲染性能;

不能像用于瀏覽器環(huán)境的輸出代碼那樣把 node_modules 里的第三方模塊和 Node.js 原生模塊(例如 fs 模塊)打包進去,而是需要通過 CommonJS 規(guī)范去引入這些模塊。

需要通過 CommonJS 規(guī)范導(dǎo)出一個渲染函數(shù),以用于在 HTTP 服務(wù)器中去執(zhí)行這個渲染函數(shù),渲染出 HTML 內(nèi)容返回。

解決方案

用于構(gòu)建瀏覽器環(huán)境代碼的 webpack.config.js 配置文件保留不變,新建一個專門用于構(gòu)建服務(wù)端渲染代碼的配置文件 webpack_server.config.js,內(nèi)容如下:

const path = require("path");
const nodeExternals = require("webpack-node-externals");

module.exports = {
  // JS 執(zhí)行入口文件
  entry: "./main_server.js",
  // 為了不把 Node.js 內(nèi)置的模塊打包進輸出文件中,例如 fs net 模塊等
  target: "node",
  // 為了不把 node_modules 目錄下的第三方模塊打包進輸出文件中
  externals: [nodeExternals()],
  output: {
    // 為了以 CommonJS2 規(guī)范導(dǎo)出渲染函數(shù),以給采用 Node.js 編寫的 HTTP 服務(wù)調(diào)用
    libraryTarget: "commonjs2",
    // 把最終可在 Node.js 中運行的代碼輸出到一個 bundle_server.js 文件
    filename: "bundle_server.js",
    // 輸出文件都放到 dist 目錄下
    path: path.resolve(__dirname, "./dist"),
  },
  module: {
    rules: [
      {
        test: /.js$/,
        use: ["babel-loader"],
        exclude: path.resolve(__dirname, "node_modules"),
      },
      {
        // CSS 代碼不能被打包進用于服務(wù)端的代碼中去,忽略掉 CSS 文件
        test: /.css/,
        use: ["ignore-loader"],
      },
    ]
  },
  devtool: "source-map" // 輸出 source-map 方便直接調(diào)試 ES6 源碼
};

以上代碼有幾個關(guān)鍵的地方,分別是:

target: "node" 由于輸出代碼的運行環(huán)境是 Node.js,源碼中依賴的 Node.js 原生模塊沒必要打包進去;

externals: [nodeExternals()] webpack-node-externals 的目的是為了防止 node_modules 目錄下的第三方模塊被打包進去,因為 Node.js 默認會去 node_modules 目錄下尋找和使用第三方模塊;

{test: /.css/, use: ["ignore-loader"]} 忽略掉依賴的 CSS 文件,CSS 會影響服務(wù)端渲染性能,又是做服務(wù)端渲不重要的部分;

libraryTarget: "commonjs2" 以 CommonJS2 規(guī)范導(dǎo)出渲染函數(shù),以供給采用 Node.js 編寫的 HTTP 服務(wù)器代碼調(diào)用。

為了最大限度的復(fù)用代碼,需要調(diào)整下目錄結(jié)構(gòu):

把頁面的根組件放到一個多帶帶的文件 AppComponent.js,該文件只能包含根組件的代碼,不能包含渲染入口的代碼,而且需要導(dǎo)出根組件以供給渲染入口調(diào)用,AppComponent.js 內(nèi)容如下:

import React, { Component } from "react";
import "./main.css";

export class AppComponent extends Component {
  render() {
    return 

Hello,Webpack

} }

分別為不同環(huán)境的渲染入口寫兩份不同的文件,分別是用于瀏覽器端渲染 DOM 的 main_browser.js 文件,和用于服務(wù)端渲染 HTML 字符串的 main_server.js 文件。

main_browser.js 文件內(nèi)容如下:

import React from "react";
import { render } from "react-dom";
import { AppComponent } from "./AppComponent";

// 把根組件渲染到 DOM 樹上
render(, window.document.getElementById("app"));

main_server.js 文件內(nèi)容如下:

為了能把渲染的完整 HTML 文件通過 HTTP 服務(wù)返回給請求端,還需要通過用 Node.js 編寫一個 HTTP 服務(wù)器。 由于本節(jié)不專注于將 HTTP 服務(wù)器的實現(xiàn),就采用了 ExpressJS 來實現(xiàn),http_server.js 文件內(nèi)容如下:

const express = require("express");
const { render } = require("./dist/bundle_server");
const app = express();

// 調(diào)用構(gòu)建出的 bundle_server.js 中暴露出的渲染函數(shù),再拼接下 HTML 模版,形成完整的 HTML 文件
app.get("/", function (req, res) {
  res.send(`


  


${render()}
`); }); // 其它請求路徑返回對應(yīng)的本地文件 app.use(express.static(".")); app.listen(3000, function () { console.log("app listening on port 3000!") });

再安裝新引入的第三方依賴:

# 安裝 Webpack 構(gòu)建依賴
npm i -D css-loader style-loader ignore-loader webpack-node-externals
# 安裝 HTTP 服務(wù)器依賴
npm i -S express

以上所有準備工作已經(jīng)完成,接下來執(zhí)行構(gòu)建,編譯出目標文件:

執(zhí)行命令 webpack --config webpack_server.config.js 構(gòu)建出用于服務(wù)端渲染的 ./dist/bundle_server.js 文件。

執(zhí)行命令 webpack 構(gòu)建出用于瀏覽器環(huán)境運行的 ./dist/bundle_browser.js 文件,默認的配置文件為 webpack.config.js

構(gòu)建執(zhí)行完成后,執(zhí)行 node ./http_server.js 啟動 HTTP 服務(wù)器后,再用瀏覽器去訪問 http://localhost:3000 就能看到 Hello,Webpack 了。 但是為了驗證服務(wù)端渲染的結(jié)果,你需要打開瀏覽器的開發(fā)工具中的網(wǎng)絡(luò)抓包一欄,再重新刷新瀏覽器后,就能抓到請求 HTML 的包了,抓包效果圖如下:

可以看到服務(wù)器返回的是渲染出內(nèi)容后的 HTML 而不是 HTML 模版,這說明同構(gòu)應(yīng)用的改造完成。

本實例提供項目完整代碼
構(gòu)建Electron應(yīng)用

Electron 是 Node.js 和 Chromium 瀏覽器的結(jié)合體,用 Chromium 瀏覽器顯示出的 Web 頁面作為應(yīng)用的 GUI,通過 Node.js 去和操作系統(tǒng)交互。 當(dāng)你在 Electron 應(yīng)用中的一個窗口操作時,實際上是在操作一個網(wǎng)頁。當(dāng)你的操作需要通過操作系統(tǒng)去完成時,網(wǎng)頁會通過 Node.js 去和操作系統(tǒng)交互。

采用這種方式開發(fā)桌面端應(yīng)用的優(yōu)點有:

降低開發(fā)門檻,只需掌握網(wǎng)頁開發(fā)技術(shù)和 Node.js 即可,大量的 Web 開發(fā)技術(shù)和現(xiàn)成庫可以復(fù)用于 Electron;

由于 Chromium 瀏覽器和 Node.js 都是跨平臺的,Electron 能做到寫一份代碼在不同的操作系統(tǒng)運行。

在運行 Electron 應(yīng)用時,會從啟動一個主進程開始。主進程的啟動是通過 Node.js 去執(zhí)行一個入口 JavaScript 文件實現(xiàn)的,這個入口文件 main.js 內(nèi)容如下:

See the Pen Electron-main.js by whjin (@whjin) on CodePen.


閱讀需要支付1元查看
<