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

資訊專欄INFORMATION COLUMN

webpack實戰

cyrils / 1009人閱讀

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

webpack實戰
查看所有文檔頁面:全棧開發,獲取更多信息。

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

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

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

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

使用新語言來開發項目:

使用 ES6 語言

使用 TypeScript 語言

使用 Flow 檢查器

使用 SCSS 語言

使用 PostCSS

使用新框架來開發項目:

使用 React 框架

使用 Vue 框架

使用 Angular2 框架

用 Webpack 構建單頁應用:

為單頁應用生成 HTML

管理多個單頁應用

用 Webpack 構建不同運行環境的項目:

構建同構應用

構建 Electron 應用

構建 Npm 模塊

構建離線應用

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

搭配 Npm Script

檢查代碼

通過 Node.js API 啟動 Webpack

使用 Webpack Dev Middleware

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

加載圖片

加載SVG

加載 Source Map

使用 TypeScript 語言

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

使用 Angular2 框架

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

使用ES6語言

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

把新的 ES6 語法用 ES5 實現,例如 ES6 的 class 語法用 ES5 的 prototype 實現。

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

Babel

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

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

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

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

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

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

npm i -D babel-plugin-transform-runtime

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

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

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;
}

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

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 要轉換的源碼使用了哪些新的語法特性,一個 Presets 對一組新語法特性提供支持,多個 Presets 可以疊加。

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

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

env 包含當前所有 ECMAScript 標準里的最新特性。

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

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

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

stage2 該特性規范已經被起草,將會被納入標準里;

stage3 該特性規范已經定稿,各大瀏覽器廠商和 Node.js 社區開始著手實現;

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

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

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

接入 Babel

由于 Babel 所做的事情是轉換代碼,所以應該通過 Loader 去接入 Babel,Webpack 配置如下:

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

配置命中了項目目錄下所有的 JavaScript 文件,通過 babel-loader 去調用 Babel 完成轉換工作。 在重新執行構建前,需要先安裝新引入的依賴:

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

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

$blue: #1875e7; 

div {
  color: $blue;
}

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

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

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

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

npm i -g node-sass

再執行編譯命令:

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

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

接入 Webpack

Webpack 接入 sass-loader 相關配置如下:

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 源碼轉換為 CSS 代碼,再把 CSS 代碼交給 css-loader 去處理。

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

style-loader 會把 CSS 代碼轉換成字符串后,注入到 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 靜態類型檢測器,它是 JavaScript 語言的超集。

你所需要做的就是在需要的地方加上類型檢查,例如在兩個由不同人開發的模塊對接的接口出加上靜態類型檢查,能在編譯階段就指出部分模塊使用不當的問題。 同時 Flow 也能通過類型推斷檢查出 JavaScript 代碼中潛在的 Bug。

Flow 使用效果如下:

// @flow

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

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


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

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

npm i -D flow-bin

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

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

再通過 npm run flow 去調用 Flow 執行代碼檢查。

除此之外你還可以通過:

npm i -g flow-bin

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

安裝成功后,在項目根目錄下執行 Flow 后,Flow 會遍歷出所有需要檢查的文件并對其進行檢查,輸出錯誤結果到控制臺。

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

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

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

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

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

babel-preset-flow 與 Babel 集成。

集成 Webpack

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

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

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

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

往源碼里加入靜態類型后重新構建項目,你會發現采用了 Flow 的源碼還是能正常在瀏覽器中運行。

要明確構建的目的只是為了去除源碼中的 Flow 靜態類型語法,而代碼檢查和構建無關。 許多編輯器已經整合 Flow,可以實時在代碼中高亮指出 Flow 檢查出的問題。
使用PostCSS

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

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

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

給 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 代碼調用的模塊,也提供了可執行的文件。

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

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

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

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

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

接入 PostCSS 相關的 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
# 根據你使用的特性安裝對應的 PostCSS 插件依賴
npm i -D postcss-cssnext
    
使用React框架 React 語法特征

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

class Button extends Component {
  render() {
    return 

Hello,Webpack

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

其中 JSX 語法是無法在任何現有的 JavaScript 引擎中運行的,所以在構建過程中需要把源碼轉換成可以運行的代碼,例如:

// 原 JSX 語法代碼
return 

Hello,Webpack

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

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

通過以下命令:

# 安裝 React 基礎依賴
npm i -D react react-dom
# 安裝 babel 完成語法轉換所需依賴
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(

重新執行構建打開網頁你將會發現由 React 渲染出來的 Hello,Webpack

React 與 TypeScript

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

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

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

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

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

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

module.exports = {
  // TS 執行入口文件
  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 方便在瀏覽器里調試 TypeScript 代碼
};

通過npm i react react-dom @types/react @types/react-dom安裝新的依賴后重啟構建,重新打開網頁你將會發現由 React 渲染出來的 Hello,Webpack

使用Vue框架

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

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

main.js 入口文件:

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

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

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

接入 Webpack

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

修改 Webpack 相關配置如下:

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

安裝新引入的依賴:

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

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

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

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

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

使用 TypeScript 編寫 Vue 應用

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

新增 tsconfig.json 配置文件,內容如下:

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

修改 App.vue 腳本部分內容如下:



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

修改 main.ts 執行入口文件為如下:

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

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

由于 TypeScript 不認識 .vue 結尾的文件,為了讓其支持 import App from "./App.vue" 導入語句,還需要以下文件 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 文件當成一個 TypeScript 模塊去處理,以解決 moudle not found 的問題,tsc 本身不會處理 .vue 結尾的文件
          appendTsSuffixTo: [/.vue$/],
        }
      },
    ]
  },
};

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

為單頁應用生成HTML 引入問題

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

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

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

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

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

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

在開始前先來看看該應用最終發布到線上的代碼。

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

解決方案

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

首先,修改 Webpack 配置。

以上配置中,大多數都是按照前面已經講過的內容增加的配置,例如:

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

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

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

壓縮輸出的 JavaScript 代碼。

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

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

其中 template: "./template.html" 所指的模版文件 template.html 的內容是:


  
  
  
  
  
  
  


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

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

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

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

除了 _inline 表示內嵌外,還支持以下屬性:

_dist 只有在生產環境下才引入該資源;

_dev 只有在開發環境下才引入該資源;

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

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

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

管理多個單頁應用 引入問題

在開始前先來看看該應用最終發布到線上的代碼。














構建出的目錄結構為:

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

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

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

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

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

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

解決方案

項目源碼目錄結構如下:

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

從目錄結構中可以看成出下幾點要求:

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

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

每個單頁應用的目錄下都有一個 index.js 文件作為入口執行文件。

雖然 AutoWebPlugin 強制性的規定了項目部分的目錄結構,但從實戰經驗來看這是一種優雅的目錄規范,合理的拆分了代碼,又能讓新人快速的看懂項目結構,也方便日后的維護。

Webpack 配置文件修改如下:

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


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

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

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

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

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

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

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

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

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

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

由于 AutoWebPlugin 是間接的通過上一節提到的 WebPlugin 實現的,WebPlugin 支持的功能 AutoWebPlugin 都支持。

構建同構應用

同構應用是指寫一份代碼但可同時在瀏覽器和服務器中運行的應用。

認識同構應用

現在大多數單頁應用的視圖都是通過 JavaScript 代碼在瀏覽器端渲染出來的,但在瀏覽器端渲染的壞處有:

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

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

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

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

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

虛擬 DOM 的優點在于:

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

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

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

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

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

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

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

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

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

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

需要通過 CommonJS 規范導出一個渲染函數,以用于在 HTTP 服務器中去執行這個渲染函數,渲染出 HTML 內容返回。

解決方案

用于構建瀏覽器環境代碼的 webpack.config.js 配置文件保留不變,新建一個專門用于構建服務端渲染代碼的配置文件 webpack_server.config.js,內容如下:

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

module.exports = {
  // JS 執行入口文件
  entry: "./main_server.js",
  // 為了不把 Node.js 內置的模塊打包進輸出文件中,例如 fs net 模塊等
  target: "node",
  // 為了不把 node_modules 目錄下的第三方模塊打包進輸出文件中
  externals: [nodeExternals()],
  output: {
    // 為了以 CommonJS2 規范導出渲染函數,以給采用 Node.js 編寫的 HTTP 服務調用
    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 代碼不能被打包進用于服務端的代碼中去,忽略掉 CSS 文件
        test: /.css/,
        use: ["ignore-loader"],
      },
    ]
  },
  devtool: "source-map" // 輸出 source-map 方便直接調試 ES6 源碼
};

以上代碼有幾個關鍵的地方,分別是:

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

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

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

libraryTarget: "commonjs2" 以 CommonJS2 規范導出渲染函數,以供給采用 Node.js 編寫的 HTTP 服務器代碼調用。

為了最大限度的復用代碼,需要調整下目錄結構:

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

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

export class AppComponent extends Component {
  render() {
    return 

Hello,Webpack

} }

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

main_browser.js 文件內容如下:

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

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

main_server.js 文件內容如下:

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

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

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


  


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

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

# 安裝 Webpack 構建依賴
npm i -D css-loader style-loader ignore-loader webpack-node-externals
# 安裝 HTTP 服務器依賴
npm i -S express

以上所有準備工作已經完成,接下來執行構建,編譯出目標文件:

執行命令 webpack --config webpack_server.config.js 構建出用于服務端渲染的 ./dist/bundle_server.js 文件。

執行命令 webpack 構建出用于瀏覽器環境運行的 ./dist/bundle_browser.js 文件,默認的配置文件為 webpack.config.js

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

可以看到服務器返回的是渲染出內容后的 HTML 而不是 HTML 模版,這說明同構應用的改造完成。

本實例提供項目完整代碼
構建Electron應用

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

采用這種方式開發桌面端應用的優點有:

降低開發門檻,只需掌握網頁開發技術和 Node.js 即可,大量的 Web 開發技術和現成庫可以復用于 Electron;

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

在運行 Electron 應用時,會從啟動一個主進程開始。主進程的啟動是通過 Node.js 去執行一個入口 JavaScript 文件實現的,這個入口文件 main.js 內容如下:

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


閱讀需要支付1元查看
<