管理Webpack依赖关系指南

本文概述

模块化的概念是大多数现代编程语言的固有组成部分。但是, 在最新版本的ECMAScript ES6到来之前, JavaScript缺乏任何正式的模块化方法。

在Node.js(当今最流行的JavaScript框架之一)中, 模块捆绑程序允许在Web浏览器中加载NPM模块, 而面向组件的库(如React)则鼓励并促进JavaScript代码的模块化。

Webpack是可用的模块捆绑器之一, 可将JavaScript代码以及所有静态资产(例如样式表, 图像和字体)处理到捆绑的文件中。处理可以包括用于管理和优化代码依存关系的所有必要任务, 例如编译, 串联, 最小化和压缩。

Webpack:初学者教程

但是, 配置Webpack及其依赖项可能会很麻烦, 而且并非总是一个简单的过程, 特别是对于初学者而言。

这篇博客文章提供了有关如何为不同场景配置Webpack的指南和示例, 并指出了与使用Webpack捆绑项目依赖项有关的最常见陷阱。

该博客文章的第一部分说明了如何简化项目中依赖项的定义。接下来, 我们讨论并演示用于多页和单页应用程序的代码拆分的配置。最后, 如果要在项目中包含第三方库, 我们将讨论如何配置Webpack。

配置别名和相对路径

相对路径与依赖关系不直接相关, 但是在定义依赖关系时会使用它们。如果项目文件结构复杂, 则可能很难解析相关的模块路径。 Webpack配置的最基本好处之一是, 它有助于简化项目中相对路径的定义。

假设我们具有以下项目结构:

- Project
    - node_modules
    - bower_modules
    - src
        - script
        - components	
            - Modal.js
            - Navigation.js
        - containers
            - Home.js
            - Admin.js

我们可以通过所需文件的相对路径来引用依赖关系, 如果我们想将组件导入源代码中的容器中, 则如下所示:

Home.js

Import Modal from ‘../components/Modal’;
Import Navigation from ‘../components/Navigation’;

Modal.js

import {datepicker} from '../../../../bower_modules/datepicker/dist/js/datepicker';

每次我们要导入脚本或模块时, 都需要知道当前目录的位置, 并找到要导入的内容的相对路径。我们可以想象, 如果我们有一个带有嵌套文件结构的大项目, 或者想要重构复杂项目结构的某些部分, 那么这个问题将如何变得复杂。

我们可以使用Webpack的resolve.alias选项轻松解决此问题。我们可以声明所谓的别名-目录或模块的名称及其位置, 并且我们不依赖项目源代码中的相对路径。

webpack.config.js

resolve: {
    alias: {
        'node_modules': path.join(__dirname, 'node_modules'), 'bower_modules': path.join(__dirname, 'bower_modules'), }
}

现在, 在Modal.js文件中, 我们可以更简单地导入datepicker:

import {datepicker} from 'bower_modules/datepicker/dist/js/datepicker';

代码分割

在某些情况下, 我们需要将脚本附加到最终捆绑包中, 或者拆分最终捆绑包, 或者我们希望按需加载单独的捆绑包。针对这些场景设置我们的项目和Webpack配置可能并不容易。

在Webpack配置中, Entry选项告诉Webpack起点是最终捆绑包的位置。入口点可以具有三种不同的数据类型:字符串, 数组或对象。

如果我们只有一个起点, 则可以使用这些格式中的任何一种并获得相同的结果。

如果我们要追加多个文件, 并且彼此之间不依赖, 则可以使用数组格式。例如, 我们可以将analytics.js附加到bundle.js的末尾:

webpack.config.js

module.exports = {
    // creates a bundle out of index.js and then append analytics.js
    entry: ['./src/script/index.jsx', './src/script/analytics.js'], output: {
        path: './build', filename: bundle.js '  
   }
};

管理多个入口点

假设我们有一个包含多个HTML文件的多页应用程序, 例如index.html和admin.html。我们可以通过使用入口点作为对象类型来生成多个包。以下配置生成两个JavaScript捆绑包:

webpack.config.js

module.exports = {
   entry: {
       index: './src/script/index.jsx', admin: './src/script/admin.jsx'
   }, output: {
       path: './build', filename: '[name].js' // template based on keys in entry above (index.js & admin.js)
   }
};

index.html

<script src="build/index.js"></script>

admin.html

<script src="build/admin.js"></script>

这两个JavaScript包都可以共享通用的库和组件。为此, 我们可以使用CommonsChunkPlugin, 它查找出现在多个条目块中的模块, 并创建可以在多个页面之间缓存的共享包。

webpack.config.js

var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js');
 
module.exports = {
   entry: {
       index: './src/script/index.jsx', admin: './src/script/admin.jsx'
   }, output: {
       path: './build', filename: '[name].js' // template based on keys in entry above (index.js & admin.js)
   }, plugins: [commonsPlugin]
};

现在, 我们不要忘记在捆绑的脚本之前添加<script src =” build / common.js”> </ script>。

启用延迟加载

Webpack可以将静态资产拆分为较小的块, 并且此方法比标准串联更灵活。如果我们有一个大型的单页应用程序(SPA), 将简单的串联连接到一个包中不是一个好方法, 因为加载一个庞大的包可能很慢, 并且用户通常不需要每个视图上的所有依赖项。

前面我们已经解释了如何将应用程序拆分为多个捆绑包, 连接常见的依赖关系以及如何从浏览器缓存行为中受益。这种方法对于多页应用程序非常有效, 但不适用于单页应用程序。

对于SPA, 我们应仅提供呈现当前视图所需的那些静态资产。 SPA架构中的客户端路由器是处理代码拆分的理想场所。当用户输入路线时, 我们只能加载结果视图所需的那些依赖项。另外, 我们可以在用户向下滚动页面时加载依赖项。

为此, 我们可以使用Webpack可以静态检测到的require.ensure或System.import函数。 Webpack可以基于此拆分点生成单独的捆绑包, 并根据需要调用它。

在此示例中, 我们有两个React容器;管理员视图和仪表板视图。

admin.jsx

import React, {Component} from 'react';
 
export default class Admin extends Component {
   render() {
       return <div > Admin < /div>;
   }
}

dashboard.jsx

import React, {Component} from 'react';
 
export default class Dashboard extends Component {
   render() {
       return <div > Dashboard < /div>;
   }
}

如果用户输入/ dashboard或/ admin URL, 则仅加载相应的必需JavaScript捆绑包。下面我们可以看到带有和不带有客户端路由器的示例。

index.jsx

if (window.location.pathname === '/dashboard') {
   require.ensure([], function() {
       require('./containers/dashboard').default;
   });
} else if (window.location.pathname === '/admin') {
   require.ensure([], function() {
       require('./containers/admin').default;
   });
}

index.jsx

ReactDOM.render(
   <Router>
       <Route path="/" component={props => <div>{props.children}</div>}>
           <IndexRoute component={Home} />
           <Route path="dashboard" getComponent={(nextState, cb) => {
               require.ensure([], function (require) {
                   cb(null, require('./containers/dashboard').default)
               }, "dashboard")}}
           />
           <Route path="admin" getComponent={(nextState, cb) => {
               require.ensure([], function (require) {
                   cb(null, require('./containers/admin').default)
               }, "admin")}}
           />
       </Route>
   </Router>
   , document.getElementById('content')
);

将样式提取到单独的捆绑包中

在Webpack中, 诸如样式加载器和css-loader之类的加载器会对样式表进行预处理, 并将其嵌入到输出JavaScript包中, 但是在某些情况下, 它们会导致未样式化内容(FOUC)的泛滥。

我们可以使用ExtractTextWebpackPlugin避免FOUC, 该方法允许将所有样式生成为单独的CSS包, 而不是将它们嵌入最终的JavaScript包中。

webpack.config.js

var ExtractTextPlugin = require('extract-text-webpack-plugin');
 
module.exports = {
   module: {
       loaders: [{
           test: /\.css/, loader: ExtractTextPlugin.extract('style', 'css’)'
       }], }, plugins: [
       // output extracted CSS to a file
       new ExtractTextPlugin('[name].[chunkhash].css')
   ]
}

处理第三方库和插件

很多时候, 我们需要使用第三方库, 各种插件或其他脚本, 因为我们不想花时间从头开始开发相同的组件。有许多可用的旧版库和插件未得到积极维护, 无法理解JavaScript模块, 并假定全局以预定义名称存在依赖项。

以下是jQuery插件的一些示例, 并说明了如何正确配置Webpack以便能够生成最终捆绑包。

ProvidePlugin

大多数第三方插件都依赖于特定的全局依赖项。对于jQuery, 插件依赖于定义的$或jQuery变量, 我们可以通过在代码中调用$(‘div.content’)。pluginFunc()来使用jQuery插件。

我们可以使用Webpack插件ProvidePlugin在每次遇到全局$标识符时在var $ = require(” jquery”)之前添加前缀。

webpack.config.js

webpack.ProvidePlugin({
   ‘$’: ‘jquery’, })

当Webpack处理代码时, 它将查找状态$, 并提供对全局依赖项的引用, 而无需导入require函数指定的模块。

进口装载机

一些jQuery插件在全局命名空间中采用$或依靠$作为窗口对象。为此, 我们可以使用imports-loader将全局变量注入模块。

example.js

$(‘div.content’).pluginFunc();

然后, 我们可以通过配置imports-loader将$变量注入模块:

require("imports?$=jquery!./example.js");

这只是为var $ = require(” jquery”);到example.js。

在第二个用例中:

webpack.config.js

module: {
   loaders: [{
       test: /jquery-plugin/, loader: 'imports?jQuery=jquery, $=jquery, this=>window'
   }]
}

通过使用=>符号(不要与ES6 Arrow函数混淆), 我们可以设置任意变量。最后一个值重新定义此全局变量以指向窗口对象。这与使用(function(){…})。call(window);包装文件的整个内容相同。并以window作为参数调用此函数。

我们还可以要求使用CommonJS或AMD模块格式的库:

// CommonJS
var $ = require("jquery");  
// jquery is available

// AMD
define([‘jquery’], function($) {  
// jquery is available
});

一些库和模块可以支持不同的模块格式。

在下一个示例中, 我们有一个jQuery插件, 该插件使用AMD和CommonJS模块格式并具有jQuery依赖性:

jquery-plugin.js

(function(factory) {
   if (typeof define === 'function' && define.amd) {
       // AMD format is used
       define(['jquery'], factory);
   } else if (typeof exports === 'object') {
       // CommonJS format is used
       module.exports = factory(require('jquery'));
   } else {
       // Neither AMD nor CommonJS used. Use global variables.
   }
});

webpack.config.js

module: {
   loaders: [{
       test: /jquery-plugin/, loader: "imports?define=>false, exports=>false"
   }]
}

我们可以选择特定库要使用的模块格式。如果我们声明define等于false, 则Webpack不会以AMD模块格式解析模块;如果我们将变量export声明为等于false, 则Webpack不会以CommonJS模块格式解析模块。

曝光装载机

如果需要将模块公开给全局上下文, 则可以使用Exposure-loader。例如, 如果我们拥有的外部脚本不是Webpack配置的一部分, 并且依赖于全局命名空间中的符号, 或者我们使用需要在浏览器控制台中访问符号的浏览器插件, 这可能会有所帮助。

webpack.config.js

module: {
   loaders: [
       test: require.resolve('jquery'), loader: 'expose-loader?jQuery!expose-loader?$'
   ]
}

jQuery库现在在全局名称空间中可用于网页上的其他脚本。

window.$
window.jQuery

配置外部依赖关系

如果要包括来自外部托管脚本的模块, 则需要在配置中定义它们。否则, Webpack无法生成最终捆绑包。

我们可以使用Webpack配置中的externals选项来配置外部脚本。例如, 我们可以通过单独的<script>标记使用CDN中的库, 同时仍将其明确声明为项目中的模块依赖项。

webpack.config.js

externals: {
   react: 'React', 'react-dom': 'ReactDOM'
}

支持库的多个实例

最好在前端开发中使用NPM软件包管理器来管理第三方库和依赖项。但是, 有时我们可以在同一个库中有多个实例, 它们具有不同的版本, 并且它们在一个环境中不能很好地协同工作。

例如, 这可能发生在React库中, 我们可以在该库中从NPM安装React, 之后可以通过一些附加的软件包或插件使用不同版本的React。我们的项目结构如下所示:

project
|
|-- node_modules
    |
    |-- react
    |-- react-plugin
        |
        |--node_modules
            |
            |--react

来自react-plugin的组件与项目中的其余组件具有不同的React实例。现在我们有两个独立的React副本, 它们可以是不同的版本。在我们的应用程序中, 这种情况可能会使我们的全局可变DOM混乱, 并且我们可以在Web控制台日志中看到错误消息。解决此问题的方法是在整个项目中使用相同版本的React。我们可以通过Webpack别名解决它。

webpack.config.js

module.exports = {
   resolve: {
       alias: {
           'react': path.join(__dirname, './node_modules/react'), 'react/addons': path.join(__dirname, '/node_modules/react/addons'), }
   }
}

当react-plugin尝试要求React时, 它将使用项目的node_modules中的版本。如果我们想找出我们使用的React版本, 可以在源代码中添加console.log(React.version)。

专注于开发, 而不是Webpack配置

这篇文章只是对Webpack功能和实用工具的了解。

还有许多其他的Webpack加载程序和插件可以帮助你优化和简化JavaScript捆绑。

即使你是初学者, 本指南也为你开始使用Webpack奠定了坚实的基础, 这将使你可以将更多的精力放在开发上, 而不必考虑捆绑配置。

相关:维护控制权:Pt Webpack and React指南。 1个

微信公众号
手机浏览(小程序)
0
分享到:
没有账号? 忘记密码?