关于 Webpack 基础使用的整理

背景

  • 网站由网页模式进化成 Webapp 模式

  • 网站运行在高级浏览器中,使用 HTML5、CSS3、ES6 等新技术

  • webapp 通常是单页面应用(每一个视图通过异步方式加载,导致页面初始化和使用过程会加载更多的 js 代码)

  • 前端开发基于多语言、多层次编码和组织工作,交付基于浏览器,需要保证代码和资源在浏览器端快速优雅的加载和更新,亟需模块化系统

传统方式

<script src="module1.js"></scrpti>
<script src="module2.js"></scrpti>
<script src="module3.js"></scrpti>
...

弊端

  • 全局作用域(定义在 window 对象下)下易造成变量冲突

  • 文件只能按照脚本引入的顺序加载

  • 需要主观解决模块和代码库的依赖关系

  • 大型项目中资源难以管理,长期积累导致代码库混乱不堪

CommonJS

服务端的 Node.js 遵循 CommonJS 规范

核心思想

  1. 允许模块通过 require 方法来同步加载要依赖的其他模块
  2. 通过 exports 或 module.exports 导出需要暴露的接口
require("module");
require("../file.js");
exports.doStuff = function() {};
module.exports = someValue;

优势

  • 服务端模块便于重用
  • NPM 中已有大量可用模块包(20w)
  • 简单易用

缺陷

  • 同步的模块加载方式不适合在浏览器环境中,同步意味着阻塞加载,浏览器资源是异步加载的
  • 不能非阻塞的并行加载多个模块

ES6 模块

ES6 标准增加了 js 语言层面的模块体系定义

ES6 的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。

优势

  • 易于进行静态分析
  • 面向未来 ES 标准

缺陷

  • 原生浏览器未实现该标准
  • 全新命令字,新版 Node.js 才支持

静态分析

编译时对整个代码进行静态分析,分析出各模块的类型依赖关系,将不同类型的模块交由相应的加载器处理。

Webpack 概念

Webpack 是一个模块打包器。

根据模块的依赖关系进行静态分析,然后将模块按照指定的规则生成对应的静态资源

现有模块化工具无法实现

  • 将依赖树拆分成按需加载的块
  • 初始化加载的耗时尽量少
  • 各种静态资源都可视作模块
  • 将第三方库整合成模块
  • 自定义打包逻辑
  • 适合无论单页或多页的 Web 应用大项目

Webpack 特点

  • 代码拆分
  • Loader
  • 智能解析
  • 插件系统
  • 快速运行

代码拆分

  • 两种组织模块依赖的方式:同步、异步、
  • 异步依赖作为分割点,形成新的块
  • 优化了依赖树后,每一个异步区块都作为一个文件被打包

Loader

  • Webpack 本身只能处理原生 js 模块
  • loader 转换器可将各种类型的资源转换成 js 模块

智能解析

Webpack 有一个智能解析器,几乎可以处理任何第三方库

无论模块形式是 CommonJS 或是普通 js 文件

加载依赖时甚至允许动态表达式

require("./templates/" + name + ".jade")

插件系统

Webpack 有一个功能丰富的插件系统

可开发和使用开源插件满足各式需求

快速运行

Webpack 使用异步 I/O 和多级缓存提高运行效率

安装

Webpack 需要 Node.js v0.6 以上支持

使用 npm 全局安装

npm install webpack -g

将 Webpack 安装到项目的依赖中

npm install webpack --save-dev

使用

index.html

<html>
<head>
  <meta charset="utf-8">
</head>
<body>
  <scritp src="bundle.js"></script>
</body>
</html>

entry.js

document.write('Hello world')

编译 entry.js 并打包到 bundle.js

webpack entry.js bundle.js

访问 index.html 即可

添加模块

module.js

module.exports = 'It works from module.js.'

entry.js

document.write('Hello world')
document.write(require('./module.js'))

打包

webpack entry.js bundle.js

分析

webpack 分析入口文件,解析包含依赖关系的各个文件

这些文件(模块)都打包到 bundle.js

webpack 给每个模块分配一个唯一 ID 并通过 ID 索引和访问模块

页面启动时先执行 entry.js,其他模块在运行 require 时再执行

loader

背景

webpack 本身只能处理 js 模块,其他类型文件需要 loader 进行转换

loader 本身一个函数,接受源文件为参数,返回转换结果

loader 特性

  • 可通过管道方式链式调用,每个 loader 可将资源转换成任意格式并传递给下一个 loader,最后一个 loader 必须返回 js
  • 可同步或异步执行
  • 运行在 node.js 环境中,可做任何可能的事情
  • 可接受参数,以此传递配置给 loader
  • 可通过文件拓展名(或正则表达式)绑定给不同类型的文件
  • 可通过 npm 发布和安装
  • 除了通过 package.json 和 main 指定,通常的模块也可导出一个 loader 来使用
  • 可访问配置
  • 支持插件
  • 可分发出附件的任意文件

loader 命名

一般为 xxx-loader,xxx 为转换的功能,例如 json-loader

引用 loader 时可通过全名(json-loader)或短名(json)

命名规则和搜索优先级顺序在 resolveLoader.moduleTemplates API 中定义

Default: ["*-webpack-loader", "*-web-loader", "*-loader", "*"]

添加方式

  • 在 require()引用模块时添加
  • webpack 全局配置中绑定
  • 命令行方式

场景

在页面引入一个 CSS 文件 style.css

body { background: yellow; }

entry.js

// 载入style.css
require("!style-loader!css-loader!./style.css")
document.write('Hello world.')
document.write(require('./module.js'))

处理

将 style.css 看作一个模块

用 css-loader 读取

用 style-loader 把它插入页面

安装 loader

npm install css-loader style-loader

即可

改进

根据模块类型(拓展名)自动绑定需要的 loader,避免每次 require CSS 文件时都写 loader 前缀

entry.js

require("!style!css!./style.css")

修改为

require("./style.css")

打包

webpack entry.js bundle.js --module-bind "css=style-loader!css-loader"

Bingo! 效果一样

只不过是在打包环节根据模块类型绑定需要的 loader,不需要在 require 中写 loader 前缀

配置文件

上面通过在命令行在执行 webpack 时传入参数,可通过指定配置文件来执行

默认搜索当前目录 webpack.config.js

该文件是一个 node.js 模块,返回一个 json 格式的配置信息对象

可通过–config 选项指定配置文件

具体操作

创建 package.json 添加 webpack 需要的依赖

{
  "name": "my-webpack",
  "version": "1.0.0",
  "description": "A simple webpack example",
  "main": "bundle.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "webpack"
  ],
  "author": "igam",
  "license": "MIT",
  "devDependencies": {
    "css-loader": "^0.21.0",
    "style-loader": "^0.13.0",
    "webpack": "^1.12.2"
  }
}

根据 package.json 下载依赖模块

npm install

创建配置文件 webpack.config.js

var webpack = require('webpack')

module.exports = {
  entry: './entry.js',
  output: {
    path: __dirname,
    filename: 'bundle.js'
  },
  module: {
    loaders: [
      {test: /\.css$/, loader: 'style-loader!css-loader'}
    ]
  }
}

Bingo again!

插件

一般在配置文件 plugins 选项中指定

webpack 内置一些常用插件,还可通过 npm 安装第三方插件

实例

内置插件 BannerPlugin:给输出的文件头部添加注释

webpack.config.js

var webpack = require('webpack')

module.exports = {
  entry: './entry.js',
  output: {
    path: __dirname,
    filename: 'bundle.js'
  },
  module: {
    loaders: [
      {test: /\.css$/, loader: 'style-loader!css-loader'}
    ]
  },
  plugins: [
    new webpack.BannerPlugin('This file is created by igam')
  ]
}

在 bundle.js 文件头可看见效果

开发环境

编译时间长,可以通过参数让编译输出内容带有进度颜色

webpack --progress --colors

若不想每次修改模块后重新编译,可启动监听模式

开启后,没有变化的模块会在编译后缓存到内存中,而非每次重新编译

webpack --progress --colors --watch

更好选择

使用 webpack-dev-server 开发服务

将在 localhost:8080 启动一个 express 静态资源服务器

以监听模式自动运行 webpack

访问 http://localhost:8080/或 http://localhost:8080/webpack-dev-server/可浏览项目中的页面和编译后的资源输出,并通过一个 socket.io 服务实时监听变化并自动刷新

安装

npm install webpack-dev-server -g

运行

webpack -dev-server --progress --colors

故障处理

可通过参数–display-error-deatils 打印错误详情

webpack 的配置提供了 resovle 和 resolveLoader 参数设置模块解析的处理细节

resolve 配置应用层模块(被打包的模块)解析

resolveLoader 配置 loader 模块的解析

当引入通过 npm 安装的 node.js 模块时,可能出现找不到依赖的错误

Node.js 模块的依赖解析算法是通过查看模块的每一层父目录中的 node-modules 文件夹来查询依赖

当出现 Node.js 模块依赖查找失败时可尝试设置 resolve.fallback 和 resolveLoader.fallback 解决问题

module.exports = {
  resolve: { fallback: path.join(__dirname, "node_modules") },
  resolveLoader: { fallback: path.join(__dirname, "node_modules") }
}

webpack 中设计路径配置最好使用绝对路径,建议使用 path.resolve(**dirname, “app/folder”)或 path.join(**dirname, “app”, “folder”)方式配置,以兼容 Windows 环境

参考: http://zhaoda.net/webpack-handbook/index.html