1. 程式人生 > >如何在 webpack 中引入未模組化的庫,如 Zepto

如何在 webpack 中引入未模組化的庫,如 Zepto

前言

最近我在研究多頁面 webpack 模組打包的完整解決方案時,發現用 import 匯入 Zepto 時,會報 Uncaught TypeError: Cannot read property 'createElement' of undefined 錯誤,導致無法愉快地使用 Zepto。在經過一番除錯和搜尋後終於找到了解決的辦法,並且對於所有不支援模組化的庫都可以用這種方法匯入模組。zepto error

原因

Zepto 的原始碼:

JavaScript
/* Zepto v1.2.0 - zepto event ajax form ie - zeptojs.com/license */
(function
(global, factory) { if (typeof define === 'function' && define.amd) define(function() { return factory(global) }) else factory(global) }(this, function(window) { var Zepto = (function() { // ... return $ })() window.Zepto = Zepto window.$ === undefined &&
(window.$ = Zepto) return Zepto }))

可以看出,它只使用了 AMD 規範的模組匯出方法 define,沒有用 CommonJs 規範的方法 module.exports 來匯出模組,不過這不是造成報錯的原因。

先來看一下 webpack 執行模組的方法:Execute the module function

再來看一下在 webpack config 中不作任何處理,直接 import $ from 'zepto',經過 webpack 轉換的 Zepto 的模組閉包:zepto webpack transformed

以上程式碼是模組執行的閉包,化簡一下其實就是 webpack 把 AMD 規範的 define 方法轉換成了 module.export = factory(global)

,以此來獲取 factory 方法返回的物件。

在模組載入(import/require)時,webpack 會通過下面這種方法來執行模組閉包並匯入模組:

JavaScript
// The require function
function __webpack_require__(moduleId) {

  // Check if module is in cache
  if(installedModules[moduleId])
    return installedModules[moduleId].exports;

  // Create a new module (and put it into the cache)
  var module = installedModules[moduleId] = {
    exports: {},
    id: moduleId,
    loaded: false,
    hot: hotCreateModule(moduleId),
    parents: hotCurrentParents,
    children: []
  }

  // Execute the module function
  modules[moduleId].call(module.exports, module, module.exports, hotCreateRequire(moduleId));

  // Flag the module as loaded
  module.loaded = true

  // Return the exports of the module
  return module.exports
}

其核心在於 modules[moduleId].call,它會傳入新初始化的 module.exports 來作為模組閉包的上下文(context),並執行模組閉包來將模組暴露的物件加入到已載入的模組物件(installedModules)中。

所以對於 Zepto 來說,它初始化時使用的 this(見下圖)其實就是 module.exports,但這個 module.exports 沒有賦值過任何變數,即 Zepto 初始化使用的 this 為空物件。

this

所以 factory(global) 中 global 為空物件,Zepto 執行函式中的 window 也就變成了空物件,而 var document = window.document,這個 document 為 undefined,因此會造成 document.createElement 會報 TypeError。

解決方法

Bash
$ npm i -D script-loader exports-loader

要用到兩個 loader:exports-loader 和 script-loader。

script-loader

JavaScript
require("script!./zepto.js");  
// => execute file.js once in global context

script-loader 可以在我們 import/require 模組時,在全域性上下文環境中執行一遍模組 JS 檔案(不管 require 幾次,模組僅執行一次)。

script-loader

script-loader 把我們指定的模組 JS 檔案轉成純字串,並用 eval.call(null, string) 執行,這樣執行的作用域就為全域性作用域了。

但如果只用 script-loader,我們要匯入 Zepto 物件就需要這麼寫:

JavaScript
// entry.js
/*
 * 不能使用 `import $ from 'zepto'`
 * 因為 zepto.js 執行後返回值為 undefined
 * 因為 module.exports 預設初始為空物件
 * 所以 $ 也為空物件
 */

$(function () { })

這樣的寫法就是:當 webpack 初始化(webpackBootstrap)時,zepto.js 會在全域性作用域下執行一遍,將 Zepto 物件賦值給 window.$ 並掛載到 window 上。因此後續的 $、Zepto 變數就都可用了。

不過這種持續依賴全域性物件的實現方法不太科學,還是將物件以 ES6 Module/CommonJs/AMD 方式暴露出來更好。

Note:

如果我們用的庫沒有把物件掛載到全域性的話,就沒法作為模組使用了(還是趁早提個 PR 模組化一下吧)。

exports-loader

為了讓我們的模組匯入更加地「模組化」,可以 import/require,而不是像上面那麼「與眾不同」,我們還需要 exports-loader 的幫助。

exports-loader 可以匯出我們指定的物件:

JavaScript
require('exports?window.Zepto!./zepto.js')  

他的作用就是在模組閉包最後加一句 module.exports = window.Zepto 來匯出我們需要的物件,這樣我們就可以愉快地 import $ from 'zepto' 了。

exports-loader

webpack 配置

廢話說了那麼多,終於可以告訴大家怎麼直接使用這兩個 loader 來「模組化」Zepto 了:

JavaScript
// webpack.config
{
  // ...
  module: {
    loaders: [{
      test: require.resolve('zepto'),
      loader: 'exports-loader?window.Zepto!script-loader'
    }]
  }
}

這樣我們在頁面入口檔案中就可以這麼寫:

JavaScript
// entry.js
import $ from 'zepto'

$(function () {
  // ...
})

Note: 
require.resolve() 是 nodejs 用來查詢模組位置的方法,返回模組的入口檔案,如 zepto 為 ./node_modules/zepto/dist/zepto.js

其他

如果你不想寫 import $ from 'zepto',並且想用其他變數來代替 Zepto。 
可以使用官方的一個外掛:webpack.ProvidePlugin。

webpack.ProvidePlugin 是一個依賴注入型別的外掛,可以讓你在使用指定變數時,比如直接使用 $ 時,自動載入指定的模組 zepto,並將其暴露的物件賦值給 $

JavaScript
// webpack.config
{
  // ...
  plugins: [
    new webpack.ProvidePlugin({
      $: 'zepto',
      // 把 Zepto 匯入為 abc 變數也可以
      abc: 'zepto'
    })
    // ...
  ]
}

這樣就可以直接使用賦值了 Zepto 物件的 $/abc 變量了~

JavaScript
// entry.js
$(function () {
  // ...
  abc(document)
})

如果不想這麼麻煩地用兩個 loader 來解決問題,可以把不支援模組化的庫模組化,比如用這個 npm 包:webpack-zepto

但這個包已經一年多沒更新了,所以我更推薦上面比較麻煩的做法來確保引入的模組是最新的

總結

由於我們用 npm 下載的模組沒有模組化,因此我們要使用:

  1. script-loader 全域性上下文環境中執行模組 JS 檔案;
  2. exports-loader 新增 module.exports 來主動暴露需要的物件,使其模組化。

這樣的方法適用於所有的庫,不過最好的解決辦法是從根本上讓這些讓這些庫支援模組化。

參考

相關推薦

何在 webpack 引入模組 Zepto

前言 最近我在研究多頁面 webpack 模組打包的完整解決方案時,發現用 import 匯入 Zepto 時,會報 Uncaught TypeError: Cannot read property 'createElement' of undefined 錯誤,

webpack引入模組檔案

webpack引入非模組化js的方法: 比如我們要在webpack專案中引入 qrcodejs(生成二維碼的外掛),外掛詳細程式碼如下: 解決辦法: 先安裝 exports-loader 檔案解析外掛: npm install -D exports-loader

MyBatissqlSession操作數據不報錯但無法實現數據修改(增、改、刪)

修改 () brush light pub clas java bat job public void addCustomerTest() throws Exception { SqlSession sqlSession = MyBatisUtils.ge

Python的Numpy模組(1numpy建立)

1.什麼是Numpy?      Numpy   (Numeric Python)      Numpy系統是Python中的一種開源的數值計算擴充套件。        

Spring Boot引入web模組

1、修改pom.xml 引入spring boot web模組 <dependency> <!-- 引入web模組 --> <groupId>org.springframework.boot</groupId> &

vue+vue-cli+webpack引入jQuery

在專案中為了方便操作dom元素,選擇jQuery,下面是步驟 首先在package.json裡的dependencies加入"jquery" : "^3.2.1",然後install   (注:可以查詢最新的jQuery版本) 在webpack.base.conf.js里

webpack構建——css的模組

    今天配置css模組化,一直出錯,最後發現原因是配置的問題。首先,看下面:webpack.config.js  :{ test: /\.css$/, use: [ 'style-loader', { loader: 'css-loa

關於.Pro檔案引入原始檔的不同寫法導致QMake無法生成正確的Makefile檔案的錯誤

緣起 今天用Qt Creator 3.4.1 開啟一個很老的工程,還是QT 4.5.2的,VS2008版本編輯的;沒有語法錯誤,卻在連結時提示"LNK2019: 無法解析的外部符號 "public:

問題:在程式儲存資料到索引發生異常jackson資料轉換異常造成死迴圈棧溢位 解決@JsonIgnore註解

org.springframework.data.elasticsearch.ElasticsearchException: failed to index the document [id: 69]     at org.springframework.data.elas

模組開發AMDCMDCommonjs規範

angular、vue、react三大框架模組管理遵循的規範 webpack打包成一個檔案,一次都載入完,需要哪個就去執行哪個。不像傳統的頁面,引入很多檔案,要一個個按順序載入。 生成的都是閉包,變數隔離,並能避免汙染作用域。 vue,angularx,r

IntelliJ IDEA Java 9 模組實戰

右鍵-New-Module,輸入模組基本資訊 新建module-info.java檔案 輸入module-info.java內容如下: module helloworld { exports

在JSP引入不了標籤,但是標籤存在,卻依舊報錯Can not find the tag library descriptor for ......

在JSP中引入不了標籤庫,但是標籤庫存在,卻依舊報錯Can not find the tag library descriptor for …… 在這種情況下只需引入 standard.jar 或

餓了麼專案---13、模組程式設計實現商品詳情頁面

import BScroll from 'better-scroll'; import cartcontrol from '../cartcontrol/cartcontrol.vue'; import ratingSelecte from '../ratingSelecte/ratingSelecte.v

一步一步DIY zepto研究zepto原始碼3 -- event模組

上面的博文介紹的都是原始碼src下的zepto.js檔案,接著我們來看看zepto的事件模組,對應檔案是event.js 1.繫結事件 例項Demo <div id="foo1">foo1</div> &

前端採用SeaJs模組程式設計處理web專案版本迭代每次都清空瀏覽器快取問題

1.首先定製規則,業務程式碼開發的js我的在app0資料夾下,第三方的js在common資料夾下  2.引入seaJs相關的js檔案,實現模組化程式設計 <script language="ja

webpack 版本相容性問題錯誤總結耽誤半天學習】

【webpack版本相容性問題錯誤總結,耽誤半天學習】一定不要執行npm i  XXX  -g(-d) 一定要指定版本,儘量低版本,也不最新版本,會導致不相容和指令不一樣的問題。1.安裝webpack-dev-server 報錯,說需要webpack-cli,原因,這兩個之中

vue專案在同一頁面引入多個echarts圖表 並實現封裝自適應和動態資料改變

vue-Echarts公司最近做專案需要用到圖表,以前是使用echarts,現在也是用這個,沒什麼好糾結的! 但是最近發現以前每次做圖表之類的都沒有封裝,每次做圖表都要從新去配置之類的,寫了好多重複程式碼,感覺很累啊,所以自己把圖表封裝成子元件使用,程式碼工作量減輕了很多,而

C語言初始變數的值

C語言中未初始化的變數的值是0麼 全域性變數 、靜態變數初始值為0區域性變數,自動變數初始值隨機分配 C語言中,定義區域性變數時如果未初始化,則值是隨機的,為什麼?定義區域性變數,其實就是在棧中通過移動棧指標來給程式提供一個記憶體空間和這個區域性變數名繫結。因為這段

一步一步DIY zepto研究zepto原始碼4 -- ajax模組

上面的博文介紹的都是原始碼src下的基礎模組zepto.js檔案和事件模組event.js,下面接著看另外一個獨立的模組–ajax模組ajax.js 1.ajax的過程 當global: true時。在Ajax請求生命週期內,以下這些事件將被觸發。

【20180927】【C/C++基礎知識】程式的模組設計拿球遊戲(組合問題)漢諾塔遊戲(遞迴問題)報數遊戲(斷點+記憶體除錯)遞迴與迭代

目錄 一、模組化設計思想和方法 模組化設計方法:自頂向下設計,自下向上程式設計實現的設計方法。 學生成績管理系統,若所有功能都在主函式中實現,程式的可讀性、可修改性都很差,因此我們每個功能分別用一個函式來實現,主函式方便快捷的呼叫這些函式即可(模組化)。