对webpack的初步研究8

模块

模块化编程中,开发人员将程序分解为称为模块的离散功能块

每个模块的表面积小于完整程序,使验证,调试和测试变得微不足道。编写良好的模块提供了可靠的抽象和封装边界,因此每个模块在整个应用程序中具有一致的设计和明确的目的。

Node.js几乎从一开始就支持模块化编程。但是,在网络上,对模块的支持进展缓慢。存在多种工具,支持Web上的模块化JavaScript,具有各种优点和局限性。webpack建立在从这些系统中学到的经验教训的基础上,并将的概念应用于项目中的任何文件。

什么是webpack模块 

Node.js模块相比,webpack 模块可以以各种方式表达它们的依赖关系一些例子是:

  • 一个ES2015import声明
  • 一个CommonJS的 require()声明
  • 一个AMD definerequire声明
  • css / sass / less文件中@import语句
  • 样式表(url(...))或html(<img src=...>)文件中的图像URL 
webpack 1需要一个特定的加载器来转换ES2015 import,但是这可以通过webpack 2开箱即用

支持的模块类型 

webpack通过加载器支持用各种语言和预处理器编写的模块装载机描述到的WebPack 如何处理非JavaScript 模块和包括这些依赖关系到你的webpack社区已经为各种流行语言和语言处理器构建了加载器,包括:

还有很多人!总体而言,提供的WebPack定制一个功能强大且丰富的API,允许使用的WebPack为任何堆栈,同时保持不固执己见有关开发,测试和生产流程。

为什么选择webpack

要理解为什么要使用webpack,我们可以回顾一下在捆绑包之前我们如何在Web上使用JavaScript。

有两种方法可以在浏览器中运行JavaScript。首先,为要实现的每个功能包含一个脚本,问题是解决方案难以扩展,因为加载太多脚本会导致网络瓶颈。另一种方法是加载一个包含所有项目代码的大型.js文件,但这会导致无法维护的脚本导致范围,大小,可读性,脆弱性和整体文件出现问题。

IIFE - 立即调用函数表达式 

IIFE解决大型项目的范围问题。当脚本文件由IIFE包装时,您可以安全地连接或安全地组合文件,而无需考虑范围冲突。

这导致了Make,Gulp,Grunt,Broccoli或Brunch等工具。这些工具称为任务运行程序,除了其他目的之外,它们还用于将所有项目文件连接在一起,以解决之前提到的一些问题。

但是,无论何时想要更改一个文件,都必须重建整个文件。连接使得跨文件重用脚本变得微不足道,并使构建优化更难实现。你怎么知道什么代码被使用,哪些不是?

如果您只使用lodash中的一个函数或者来自moment.js的一个日期实用程序,那么您实际上是在添加整个库并将它们压缩在一起。你如何树木化你的代码依赖?此外,延迟加载代码块可能难以大规模实现,并且需要开发人员的大量手动工作。

由于Node.js,JavaScript模块诞生了 

webpack在Node.js上运行,Node.js是一个JavaScript运行时,可以在浏览器环境之外的计算机和服务器中使用。

当Node.js发布时,一个新的时代开始了,它带来了新的挑战。现在JavaScript没有在浏览器中运行,Node应用程序应该如何加载新的代码块?没有可以添加到其中的html文件和脚本标记。

CommonJS问世并推出require,它允许您在当前文件中加载和使用模块。这解决了开箱即用的范围问题以及使用的代码变得清晰,因为我们需要导入我们将需要的每个模块。

npm + Node.js + modules - 大规模分发 

JavaScript正在将世界作为一种语言,一种平台以及快速开发和创建快速运行的应用程序的方式。

但CommonJS没有浏览器支持。没有活动绑定循环引用存在问题。同步模块分辨率加载器很慢。虽然CommonJS是Node.js项目的绝佳解决方案,但浏览器并不支持模块。就是在创建像Browserify,RequireJS和SystemJS这样的捆绑器和工具来解决这个限制时,可以编写在浏览器中运行的CommonJS模块。

ESM - ECMAScript模块 

对于Web项目来说,好消息是模块正在成为ECMAScript标准的官方功能,虽然浏览器支持仍然很短,早期实现表明捆绑仍然更快,今天推荐。

不是很好...... 

...有一些东西不仅可以让我们编写模块,而且还支持任何模块格式(至少在我们到达ESM之前)并且可以同时处理资源和资产。

这就是webpack存在的原因。它不仅可以捆绑您的JavaScript应用程序,支持ESM和CommonJS,还可以扩展为支持所有不同类型的资源,如图像,字体和样式表。

webpack非常关注性能,它总是添加和改进异步块加载和预取等功能,以帮助您向用户提供最佳版本的项目,始终关注加载时间和性能。

模块解析

解析器是一个库,它有助于通过绝对路径定位模块。可以将模块作为来自另一模块的依赖项,如下所示:

import foo from 'path/to/module';
// or
require('path/to/module');

依赖性模块可以来自应用程序代码或第三方库。解析器帮助webpack找到需要包含在每个此类requireimport语句的包中的模块代码webpack使用enhanced-resolve来解析文件路径,同时捆绑模块。

解决webpack中的规则 

使用enhanced-resolve,webpack可以解析三种文件路径:

绝对路径 

import '/home/me/file';

import 'C:\\Users\\me\\file';

由于我们已经拥有文件的绝对路径,因此无需进一步解析。

相对路径 

import '../src/file1';
import './file2';

在这种情况下,将importrequire发生的资源文件的目录作为上下文目录。import/require此上下文路径中指定的相对路径将生成模块的绝对路径

模块路径 

import 'module';
import 'module/lib/file';

在所有指定的目录中搜索模块resolve.modules您可以使用resolve.alias配置选项为备用路径创建别名,以替换原始模块路径

根据上述规则解析路径后,解析程序会检查路径是否指向文件或目录。如果路径指向文件:

  • 如果路径具有文件扩展名,则文件将直接捆绑。
  • 否则,使用resolve.extensions选项解析文件扩展名,该选项告诉解析器哪些扩展名(例如 - .js.jsx)可以接受解析。

如果路径指向文件夹,则执行以下步骤以查找具有正确扩展名的正确文件:

  • 如果文件夹包含package.json文件,则按resolve.mainFields顺序查找配置选项中指定的字段,并且第一个此类字段package.json确定文件路径。
  • 如果没有package.json或者主字段没有返回有效路径,则按resolve.mainFiles顺序查找配置选项中指定的文件名,以查看导入/必需目录中是否存在匹配的文件名。
  • 然后使用该resolve.extensions选项以类似的方式解析文件扩展名

webpack 根据您的构建目标为这些选项提供合理的默认值

这遵循与为文件解析指定的规则相同的规则。resolveLoader配置选项可用于为加载器分别设置解决方案规则。

高速缓存 

每个文件系统访问都被缓存,因此对同一文件的多个并行或串行请求发生得更快。监视模式下,只有已修改的文件才会从缓存中逐出。如果关闭监视模式,则在每次编译之前清除缓存。

请参阅Resolve API以了解有关上述配置选项的更多信息。

依赖图

每当一个文件依赖于另一个文件时,webpack会将其视为依赖项这允许webpack获取非代码资产(如图像或Web字体),并将它们作为应用程序的依赖项提供。

当webpack处理您的应用程序时,它从命令行或其配置文件中定义的模块列表开始。从这些入口点开始,webpack递归地构建一个包含应用程序所需的每个模块依赖关系图,然后将所有这些模块捆绑到少量捆绑包中 - 通常只需一个 - 由浏览器加载。

捆绑您的应用程序对于HTTP / 1.1客户端来说尤其强大,因为它最大限度地减少了应用程序在浏览器启动新请求时必须等待的次数。对于HTTP / 2,您还可以使用Code Splitting来获得最佳结果。

清单

在使用webpack构建的典型应用程序或站点中,有三种主要类型的代码:

  1. 您和您的团队编写的源代码。
  2. 您的源所依赖的任何第三方库或“供应商”代码。
  3. 一个webpack运行时和清单,用于执行所有模块的交互。

本文将重点介绍这三个部分中的最后一部分,即运行时,特别是清单。

运行 

运行时以及清单数据基本上是在模块化应用程序在浏览器中运行时连接模块化应用程序所需的所有代码。它包含在模块交互时连接模块所需的加载和解析逻辑。这包括连接已经加载到浏览器中的模块以及延迟加载尚未加载的模块的逻辑。

表现 

一旦您的应用程序以index.html文件形式访问浏览器,您的应用程序所需的一些捆绑包和各种其他资产必须以某种方式加载和链接。/src你精心布局的那个目录现在被捆绑,缩小,甚至可能被分成更小的块,用于webpack的延迟加载optimization那么webpack如何管理所有必需模块之间的交互?这是清单数据的来源......

当编译器进入,解析并映射出您的应用程序时,它会在您的所有模块上保留详细的注释。这个数据集称为“Manifest”,它是运行时捆绑并运送到浏览器后用于解析和加载模块的内容。无论您选择哪种模块语法,这些importrequire语句现在都成为__webpack_require__指向模块标识符的方法。使用清单中的数据,运行时将能够找到检索标识符后面的模块的位置。

问题 

所以现在你对webpack如何在幕后工作有一些了解。“但是,这对我有什么影响?”,你可能会问。简单的答案是,大部分时间它没有。运行时将使用清单执行其操作,一旦应用程序到达浏览器,一切都将显得神奇地工作。但是,如果您决定通过使用浏览器缓存来提高项目性能,那么这个过程将突然变得非常重要。

通过在包文件名中使用内容哈希,可以向浏览器指示文件内容何时更改,从而使缓存无效。一旦你开始这样做,你会立即注意到一些有趣的行为。某些哈希值即使在内容显然没有变化时也会发生变化。这是由注入运行时和清单引起的,这会改变每个构建。

请参阅输出管理指南的清单部分了解如何提取清单,并阅读下面的指南,以了解有关长期缓存的复杂性的更多信息。

目标

因为可以为服务器和浏览器编写JavaScript,所以webpack提供了多个可以在webpack 配置中设置的部署目标

用法 

要设置target属性,只需在webpack配置中设置目标值:

webpack.config.js

module.exports = {
  target: 'node'
};

在上面的示例中,使用nodewebpack将编译以在类似Node.js的环境中使用(使用Node.js require加载块而不触及任何内置模块,如fspath)。

每个目标都有各种部署/环境特定的附加功能,以满足其需求。查看可用的目标

多个目标 

虽然的WebPack也没有多串被传递到支持target属性,您可以创建捆绑两个独立配置的同构库:

webpack.config.js

const path = require('path');
const serverConfig = {
  target: 'node',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'lib.node.js'
  }
  //…
};

const clientConfig = {
  target: 'web', // <=== can be omitted as default is 'web'
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'lib.js'
  }
  //…
};

module.exports = [ serverConfig, clientConfig ];

资源 

从上面的选项可以看出,您可以选择多个不同的部署目标以下是您可以参考的示例和资源的列表。

需要找到在实时代码或样板中使用的这些webpack目标的最新示例。

热模块更换

热模块更换(HMR)在应用程序运行时交换,添加或删除模块,无需完全重新加载。这可以通过以下几种方式显着加快开发速度:

  • 保留在完全重新加载期间丢失的应用程序状态。
  • 只需更新已更改的内容,即可节省宝贵的开发时间。
  • 在源代码中对CSS / JS进行的修改导致即时浏览器更新,这与在浏览器的开发工具中直接更改样式几乎相当。

怎么运行的 

让我们通过一些不同的观点来准确理解HMR的工作原理......

在应用程序中 

以下步骤允许模块交换进出应用程序:

  1. 应用程序要求HMR运行时检查更新。
  2. 运行时异步下载更新并通知应用程序。
  3. 然后,应用程序要求运行时应用更新。
  4. 运行时同步应用更新。

您可以设置HMR以便自动执行此过程,或者您可以选择要求用户交互以进行更新。

在编译器中 

除了普通资产之外,编译器还需要发出“更新”以允许从先前版本更新到新版本。“更新”包括两部分:

  1. 更新后的清单(JSON)
  2. 一个或多个更新的块(JavaScript)

清单包含新的编译哈希和所有更新的块的列表。这些块中的每一个都包含所有更新模块的新代码(或指示模块已被删除的标志)。

编译器确保模块ID和块ID在这些构建之间保持一致。它通常将这些ID存储在内存中(例如,使用webpack-dev-server),但也可以将它们存储在JSON文件中。

在一个模块中 

HMR是一种选择加入功能,仅影响包含HMR代码的模块。一个例子是通过修补样式style-loader为了使补丁工作,style-loader实现了HMR接口; 当它通过HMR收到更新时,它会用新的样式替换旧样式。

同样,在模块中实现HMR接口时,您可以描述更新模块时应该发生的情况。但是,在大多数情况下,并不是必须在每个模块中编写HMR代码。如果模块没有HMR处理程序,则更新会冒泡。这意味着单个处理程序可以更新完整的模块树。如果更新了树中的单个模块,则会重新加载整个依赖关系集。

有关该接口的详细信息,请参阅HMR API页面module.hot

在运行时 

这里的东西有点技术性......如果你对内部不感兴趣,可以随意跳转到HMR API页面HMR指南

对于模块系统运行时,会发出附加代码以跟踪模块parentschildren在管理方面,运行时支持两种方法:checkapply

check向更新清单发出HTTP请求。如果此请求失败,则没有可用的更新。如果成功,则将更新的块列表与当前加载的块列表进行比较。对于每个加载的块,下载相应的更新块。所有模块更新都存储在运行时中。当已下载所有更新块并准备应用时,运行时将切换到该ready状态。

apply方法将所有更新的模块标记为无效。对于每个无效模块,模块或其父模块中都需要有更新处理程序。否则,无效标志会冒泡并使父进程无效。每个气泡一直持续到应用程序的入口点或具有更新处理程序的模块(以先到者为准)。如果它从入口点冒泡,则该过程失败。

之后,所有无效模块都被处理(通过配置处理程序)并卸载。然后更新当前哈希并accept调用所有处理程序。运行时切换回idle状态,一切都正常继续。

入门 

HMR可以作为LiveReload替代品用于开发。webpack-dev-server支持hot在尝试重新加载整个页面之前尝试使用HMR进行更新模式。有关详细信息,请参阅热模块更换指南

与许多其他功能一样,webpack的强大功能在于其可定制性。根据特定项目的需要,许多配置HMR的方法。但是,对于大多数用途来说,这webpack-dev-server是一个很好的选择,可以让您快速开始使用HMR。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2018-09-26 13:57  又回到了起点  阅读(92)  评论(0编辑  收藏  举报