通过核心概念了解webpack工作机制

webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。 需要先理解四个核心概念:

  • 入口(entry)
  • 输出(output)
  • loader
  • 插件(plugins)

入口(entry)

入口指 webpack 构建其内部依赖图开始的模块。进入入口后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。 在 webpack 配置中有多种方式定义 entry 属性。 用法:

const config = {
  entry: {
    app: './src/app.js',
    vendors: './src/vendors.js'
  }
};

单个入口可简写成如下:

const config = {
  entry: './path/to/my/entry/file.js'
};

以下是两种实际用例: 1.分离应用程序和第三方库入口,vue-cli中是这样做的:

const config = {
  entry: {
    app: './src/app.js',
    vendors: './src/vendors.js'
  }
};

webpack 从 app.js 和 vendors.js 开始创建依赖图。这些依赖图是彼此完全分离、互相独立的。这种方式比较常见于,只有一个入口起点(不包括 vendor)的单页应用程序(single page application)中。

2.多页面应用程序

const config = {
  entry: {
    pageOne: './src/pageOne/index.js',
    pageTwo: './src/pageTwo/index.js',
    pageThree: './src/pageThree/index.js'
  }
};

在多页应用中,页面跳转时服务器将为你获取一个新的 HTML 文档。页面重新加载新文档,并且资源被重新下载。由于入口起点增多,多页应用能够复用入口起点之间的大量代码/模块.

输出(Output)

该属性设置 webpack 在输出它所创建的 bundles的路径以及命名。 注意: 即使可以存在多个入口起点,但只指定一个输出配置。

用法: 在 webpack 中配置 output 属性的最低要求是,将它的值设置为一个对象,包括以下两点:

filename 用于输出文件的文件名。 目标输出目录 path 的绝对路径。

const config = {
  output: {
    filename: 'bundle.js',
    path: '/home/proj/public/assets'
  }
};

module.exports = config; 此配置将一个单独的 bundle.js 文件输出到 /home/proj/public/assets 目录中。

多个入口时: 如果配置创建了多个单独的 "chunk"(例如,使用多个入口起点或使用像 CommonsChunkPlugin 这样的插件),则应该使用占位符(substitutions)来确保每个文件具有唯一的名称。

{
  entry: {
    app: './src/app.js',
      search: './src/search.js'
  },
  output: {
    filename: '[name].js',
      path: __dirname + '/dist'
  }
}

复杂示例: 以下是使用 CDN 和资源 hash 的复杂示例

output: {
  path: "/home/proj/cdn/assets/[hash]",
    publicPath: "http://cdn.example.com/assets/[hash]/"
}

在编译时不知道最终输出文件的 publicPath 的情况下,publicPath 可以留空,并且在入口起点文件运行时动态设置。如果你在编译时不知道 publicPath,你可以先忽略它,并且在入口起点设置 __webpack_public_path__。

__webpack_public_path__ = myRuntimePublicPath

// 剩余的应用程序入口

loader

用于对模块的源代码进行转换。loader 可以使你在 import 或"加载"模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的强大方法。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。loader 甚至允许你直接在 JavaScript 模块中 import CSS文件!

示例: 使用loader加载 CSS 文件 和 将 TypeScript 转为 JavaScript。 首先安装对应的loader

npm install --save-dev css-loader
npm install --save-dev ts-loader

然后指示 webpack 对每个 .css 使用 css-loader,以及对所有 .ts 文件使用 ts-loader:

module.exports = {
  module: {
    rules: [
      { test: /\.css$/, use: 'css-loader' },
      { test: /\.ts$/, use: 'ts-loader' }
    ]
  }
};

以上配置,对一个 module 对象定义了 rules 属性,里面包含两个必须属性:test 和 use。 test 属性,用于标识出应该被对应的 loader 进行转换的某个或某些文件。 use 属性,表示进行转换时,应该使用哪个 loader。 上面的配置代表, 当webpack编译器解析时, 遇到 require()/import 语句有'.css'路径时,在打包之前先用 css-loader 转换一下;遇到 require()/import 语句有'.ts'路径时,在打包之前先用 ts-loader 转换一下

使用Loader的三种方式:

1.配置: 在 webpack.config.js 文件中指定 loader。

module.rules 允许你在 webpack 配置中指定多个 loader。 这是展示 loader 的一种简明方式,并且有助于使代码变得简洁。同时让你对各个 loader 有个全局概览:

module: {
  rules: [
    {
      test: /\.css$/,
      use: [
        { loader: 'style-loader' },
        {
          loader: 'css-loader',
          options: {
            modules: true
          }
        }
      ]
    }
  ]
}

2.内联: 在 import 语句或任何等效于 "import" 的方式中指定 loader。

使用 ! 将资源中的 loader 分开。分开的每个部分都相对于当前目录解析。

import Styles from 'style-loader!css-loader?modules!./styles.css';

通过前置所有规则及使用 !,可以对应覆盖到配置中的任意 loader。选项可以传递查询参数,例如 ?key=value&foo=bar,或者一个 JSON 对象,例如 ?{"key":"value","foo":"bar"}。

3.通过 CLI 使用 loader:

webpack --module-bind jade-loader --module-bind 'css=style-loader!css-loader'

loader 通过(loader)预处理函数,为 JavaScript 生态系统提供了更多能力。 用户现在可以更加灵活地引入细粒度逻辑,例如压缩、打包、语言翻译等loader 遵循标准的模块解析。多数情况下,loader 将从模块路径(通常将模块路径认为是 npm install, node_modules)解析。loader 模块需要导出为一个函数,并且使用 Node.js 兼容的 JavaScript 编写。通常使用 npm 进行管理,但是也可以将自定义 loader 作为应用程序中的文件。按照约定,loader 通常被命名为 xxx-loader(例如 json-loader)。

用法: 由于插件可以携带参数/选项,你必须在 webpack 配置中,向 plugins 属性传入 new 实例。 根据你的 webpack 用法,这里有多种方式使用插件。

const HtmlWebpackPlugin = require('html-webpack-plugin'); //通过 npm 安装
const webpack = require('webpack'); //访问内置的插件
const path = require('path');

const config = {
  entry: './path/to/my/entry/file.js',
  output: {
    filename: 'my-first-webpack.bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    loaders: [
      {
        test: /\.(js|jsx)$/,
        use: 'babel-loader'
      }
    ]
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin(),
    new HtmlWebpackPlugin({template: './src/index.html'})
  ]
};

module.exports = config;

插件(plugins)

loader 被用于转换某些类型的模块,而插件的使用范围包括,从打包优化和压缩,一直到重新定义环境中的变量。可以用来处理各种任务. 使用某个插件,只需要require(),然后添加到 plugins 数组中.多数插件可以通过选项(option)自定义,也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作符来创建它的一个实例。

示例:

const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装
const webpack = require('webpack'); // 用于访问内置插件
const path = require('path');

const config = {
  entry: './src/main.js',
  output: {
    path: path.resolve(__dirname, 'dist'), //生成文件(emit)的路径
    filename: 'yq-webpack.bundle.js' // webpack bundle 的名称
  },
  module: {
    rules: [
      {test: /\.txt$/, use: 'raw-loader'}
    ]
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin(),
    new HtmlWebpackPlugin({template: './src/index.html'})
  ]
};

module.exports = config;

 模块

  在模块化编程中,开发者将程序分解成离散功能块(discrete chunks of functionality),并称之为_模块_。

每个模块具有比完整程序更小的接触面,使得校验、调试、测试轻而易举。 精心编写的_模块_提供了可靠的抽象和封装界限,使得应用程序中每个模块都具有条理清楚的设计和明确的目的。 对比 Node.js 模块,webpack _模块_能够以各种方式表达它们的依赖关系

  • ES2015 import 语句
  • CommonJS require() 语句
  • AMD define 和 require 语句
  • css/sass/less 文件中的 @import 语句。
  • 样式(url(...))或 HTML 文件(<img src=...>)中的图片链接(image url)

resolver 是一个库(library),用于帮助找到模块的绝对路径。一个模块可以作为另一个模块的依赖模块,然后被后者引用,如下:

import foo from 'path/to/module'
// 或者
require('path/to/module')

所依赖的模块可以是来自应用程序代码或第三方的库(library)。resolver 帮助 webpack 找到 bundle 中需要引入的模块代码,这些代码在包含在每个 require/import 语句中。 当打包模块时,webpack 使用 enhanced-resolve 来解析文件路径

webpack 中的解析规则

webpack 能够解析三种文件路径: 绝对路径:

import "/home/me/file";

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

相对路径

import "/home/me/file";

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

在这种情况下,使用 import 或 require 的资源文件(resource file)所在的目录被认为是上下文目录(context directory)。在 import/require 中给定的相对路径,会添加此上下文路径(context path),以产生模块的绝对路径(absolute path)。

模块路径

import "module";


import "module/lib/file";

模块将在 resolve.modules 中指定的所有目录内搜索。

一旦根据上述规则解析路径后,解析器(resolver)将检查路径是否指向文件或目录。如果路径指向一个文件:
如果路径具有文件扩展名,则被直接将文件打包。

否则,将使用 [resolve.extensions] 选项作为文件扩展名来解析,此选项告诉解析器在解析中能够接受哪些扩展名(例如 .js, .jsx)。

如果路径指向一个文件夹,则采取以下步骤找到具有正确扩展名的正确文件:

如果文件夹中包含 package.json 文件,则按照顺序查找 resolve.mainFields 配置选项中指定的字段。并且 package.json 中的第一个这样的字段确定文件路径。
如果 package.json 文件不存在或者 package.json 文件中的 main 字段没有返回一个有效路径,则按照顺序查找 resolve.mainFiles 配置选项中指定的文件名,看是否能在 import/require 目录下匹配到一个存在的文件名。
文件扩展名通过 resolve.extensions 选项采用类似的方法进行解析。
webpack 根据构建目标(build target)为这些选项提供了合理的默认配置。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大史住在大前端

express中间件系统的基本实现

一直觉得express的中间件系统这种流式处理非常形象,就好像加工流水线一样,每个环节都在针对同一个产品的不同部分完成自己的工作,最后得到一个成品。今天就来实现...

1665
来自专栏小樱的经验随笔

【Python Learning第一篇】Linux命令学习及Vim命令的使用

学了两天,终于把基本命令学完了,掌握以后可以当半个程序员了♪(^∇^*) 此文是一篇备忘录或者查询笔记,如果哪位大佬看上了并且非常嫌弃的话,还请大佬不吝赐教,多...

38310
来自专栏JAVA后端开发

vue自动保存表单功能的实现

最近想实现一个表单内容的自动保存,原来是想通过监听表单的change事件来解决.但后面想想,现在都是数据驱动了,监听数据变化就行了. 页面表单如下:

6204
来自专栏你不就像风一样

Tomcat完美配置多个HOST主机,域名,SSL

2023
来自专栏码匠的流水账

java9迁移注意事项

1、代码不模块化,先迁移到jdk9上,好利用jdk9的api 2、代码同时也模块化迁移

1481
来自专栏王二麻子IT技术交流园地

Linux常用命令100个用法

平时用linux时,我有一个习惯就是把遇到的,比较有用,并且容易忘的命令,放到一个文本文件中,没事的时候可以拿出来看看,这样可以加深映像,时间长了这些命令的用法...

2255
来自专栏程序员同行者

django权限管理(Permission)

9474
来自专栏蓝天

Tcpdump 的用法

更新时间:2005-12-26 11:55 阅读提示:第一种是关于类型的关键字,主要包括host,net,port, 例如 host 210.27.48.2,指...

1144
来自专栏LuckQI

Java多线程总结三

782
来自专栏FreeBuf

打造一款属于自己的远程控制软件(二)

前一篇文章介绍了软件的整体架构,接下来对被控端进行详细讲解,主要介绍被控端各个功能模块的关键技术以及开发过程中遇到的坑,希望对各位读者有借鉴作用。 被控端工作流...

3015

扫码关注云+社区

领取腾讯云代金券