从模块化谈起
近年来,js开发涌现出了诸多模块化解决方案,例如以在浏览器环境之外(服务端)构建 JavaScript
生态系统为目标而产生的CommonJS
规范,从CommonJS
社区中独立出来的AMD
规范(异步模块定义),还有国人制定的CMD
规范等。随着遵循AMD
规范的RequireJS
的流行,AMD
规范在前端界已被广泛认同。后来,随着npm社区的逐渐壮大,CommonJS
也越来越受欢迎,于是产生了统一这两种规范的需求,即希望提供一个前后端跨平台的解决方案,也因此产生了UMD
(通用模块定义)规范。
CommonJS
定义的是模块的同步加载,主要用于Node端;而遵循AMD规范的RequireJS
则是异步加载,适用于浏览器端。requirejs
是一种在线”编译” 模块的方案,相当于在页面上加载一个AMD 解释器,以便于览器能够识别define、exports、module
,而这些东西就是用于模块化的关键。
CommonJS
同步式的require
Node端的模块加载遵循 CommonJS
规范,该规范的核心思想是允许模块通过 require
方法来加载。
该规范首先加载所要依赖的其他模块,然后通过 exports
或 module.exports
来导出需要暴露的接口。但它的缺点也是显而易见的,即一个文件一个文件的加载很容易发生阻塞。
1 | require("module");//find from node_modules |
定义一个叫web.js
的模块,依赖于另一个叫b
的模块。如果你不想支持浏览器全局路径,那么你可以移除root
并传递this
参数在函数顶部。
define.amd
属性
为了清晰的标识全局函数(为浏览器加载script必须的)遵从AMD编程接口,任何全局函数应该有一个”amd”的属性,它的值为一个对象。
关于RequireJS的使用不在本文范围之内,因此不展开讲解,有兴趣的请移步我的另一篇文章:
详解JavaScript模块化开发:https://segmentfault.com/a/1190000000733959#articleHeader15
AMD与异步加载
因为CommonJS
阻塞式的缺点,所以并不适合前端。于是有了AMD异步加载模块的规范。
Asynchronous Module Definition
规范其实只有一个主要接口 define(id?, dependencies?, factory)
,它要在声明模块的时候指定所有的依赖 dependencies
,并且还要当做形参传到 factory
中,对于依赖的模块提前执行,依赖前置。
因为浏览器端的需求和同步require的问题,所以社区引进了异步模块加载的规范,即AMD规范。
1 | define("module", ["dep1", "dep2"], function(d1, d2) { |
使你的模块兼容于UMD规范
1 | //UMD,兼容AMD和CommonJS规范 |
UMD规范实现的思路:
- 首先判断是否支持
Node.js
模块格式,即exports
对象是否存在。 - 然后判断是否支持AMD格式(require是否存在),存在则使用
AMD
方式加载 - 若前两个都不存在,则将模块暴露到全局,
Node
即global
,浏览器即window。
例如,创建一个兼容UMD规范的jQuery插件:
1 | // Uses CommonJS, AMD or browser globals to create a jQuery plugin. |
CMD规范地址:https://github.com/seajs/seajs/issues/242
ES6 module
ECMAScript6
內建的用法:
1 | import "jquery"; |
为什么只载入JavaScript
文件?
为什么模块化系统只帮助开发者处理JavaScript
?然而还有其他静态资源需要被处理,比如:
1 | stylesheets |
1 | coffeescript ➞ javascript |
因为上面这些动机,所以有了webpack
webpack
webpack
是一款模块封装工具(module bundler,是打包工具,也是模块加载工具,各种资源都可以当成模块来处理),webpack
会将模块与其他相关联的模块,函数库,其他需要预编译的文件等整合,编译输出此模块的静态资源文件。
1 | // webpack.config.js `like=>` gulpfile.js/gruntfile.js |
在项目中使用webpack
首先在项目根目录新建一个package.json
或者通过$ npm init
指令来产生:
接着通过npm
指令安装webpack
:
1 | $ npm install webpack --save-dev |
单纯的编译指令
1 | $ webpack <entry> <output> |
配置对象内容
context:用来指明entry选项的基础目录(绝对路径)。
默认值为process.cmd()
,即webpack.config.js
文件所在路径
对象
entry
定义了打包后的入口文件,可以是数组(所有文件打包生成一个filename文件),对象或者字符串1
2
3
4
5
6
7
8
9
10
11
12{
entry: {
page1: "./page1",
page2: ["./entry1", "./entry2"]
},
output: {
// 在 output.filename 中使用 [name]或者[id],当使用多个entry points时
filename: "[name].bundle.js",
path: "dist/js/page",
chunkFilename: "[id].bundle.js" //chunkFilename是非主入口的文件名
}
}
该段代码最终会生成一个page1.bundle.js
和 page2.bundle.js
,并存放到 ./dist/js/page
文件夹下
chunkFilename
是非主入口的文件名,当按需异步加载模块的时候,这时生成的文件名是以chunkname
配置的
output
:该参数是个对象,定义了输出文件的位置及名字:path
: 打包文件存放的绝对路径publicPath
: 网站运行时的访问路径URLfilename
:打包后的文件名
你可以使用<name>=<filename>
的格式来替代 entry point
建立一个別名:
1 | // webpack.config.js |
接着执行如下命令:1
2$ webpack index=./entry.js
>> Output a file that is named index.bundle.js
对象
output
表示欲输出的路径,其会被映射到设定档中的 output.path
以及 output.filename
output.filename
:指定每一个在磁盘上输出的文件名,不允许指定绝对路径output.path
:输出绝对路径目录(必须)output.publicPath
:指定在浏览器端引用的文件公开URL地址
对象
resolve
webpack在构建包的时候会按目录进行文件的查找,resolve
属性中的extensions
数组可用于配置程序可以自行补全哪些文件后缀。extensions
第一个是空字符串,对应不需要后缀的情况。比如,为了查找CoffeeScript
文件,你的数组应当包含字符串".coffee"
。使用extensions
,在引入模块的时候就不需要写后缀,会自动补全
1 | resolve: { |
resolve.alias
定义别名
1 | alias:{ |
对象
externals
当我们想在项目中require
一些其他的类库或者API
,而又不想让这些类库的源码被构建到运行时文件中,
这在实际开发中很有必要。此时我们就可以通过配置externals
参数来解决这个问题:
1 | externals: { |
这样我们就可以放心的在项目中使用这些API了:1
var $ = require(“jquery”);
配置项详情:https://webpack.github.io/docs/configuration.html
css样式和图片的加载
你可以在你的js文件里引入css文件和图片,例如:
1 | require('./bootstrap.css'); |
当你require
了CSS(less或者其他)文件,webpack
会在页面中插入一个内联的<style>
标签去引入样式。当你require
图片的时候,bundle文件会包含图片的url
,并通过require()
返回图片的url
。
当然,你需要在webpack.config.js
里做相应的配置:
1 | // webpack.config.js |
当然,你需要先通过npm包来安装这些loader,webpack
会通过test
查找匹配文件,然后加载相应的loader
。比如通过npm安装sass loader
:
1 | $ npm install sass-loader node-sass webpack --save-dev |
实例一
新建一个content.js
1 | // content.js |
然后编辑 entry.js
加入 require
1 | // File: entry.js |
打开浏览器,看到屏幕输出:"It works from content.js";
Webpack 会给每个模块一个唯一的 ID
然后通过 ID
存取这些模块,这些模块都会被整合到 bundle.js
里面。
webpack插件和使用方法介绍
CommonsChunkPlugin
合并公共代码
它用于提取多个入口文件的公共脚本部分,然后生成一个 公共文件来方便多页面之间进行复用。以下是该插件的使用方法和webpack的具体用法介绍:
1 | var webpack = require('webpack'); |
Webpack
中将打包后的文件都称之为Chunk
。这个插件可以将多个打包后的资源中的公共部分打包成单独的文件。这里指定公共文件输出为common.js
Webpack
本身只能处理 JavaScript
模块,如果要处理其他类型的文件,就需要使用loader
进行转换。
不同模块的加载是通过模块加载器(webpack-loader
)来统一管理的。
!
用来定义loader
的串联关系,”-loader”
是可以省略不写的,多个loader
之间用“!”
连接起来,但所有的加载器都需要通过npm
来加载。
UglifyJsPlugin
压缩js文件
1 | //webpack.config.js |
Extract Text Plugin
大家都知道在webpack
中 CSS
是可以被 require()
的,webpack
会自动生成一个 <style>
标签并加入到 html
的 <head>
标签 中。但是在发布时,我们可能只希望有一个被打包过后 css 文件。这时 Extract Text Plugin
就能帮助我们完成这项任务。
1 | //webpack.config.js |
以上设置会输出一个 app.bundle.css
的文件。
html-webpack-plugin
webpack中生成HTML的插件。
1 | var HtmlWebpackPlugin = require('html-webpack-plugin') |
详情:https://www.npmjs.com/package/html-webpack-plugin
ProvidePlugin
1 | plugins: [ |
ProvidePlugin
插件可以定义一个共用的插件入口,以后的文件就不需要require('react')
也能使用React了。
babel loader
Babel-loader
能够将JSX/ES6
文件转为js文件1
$ npm install babel-loader babel-core babel-preset-es2015 babel-preset-react --save-dev
配置:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16module.exports = {
entry: {}, //文件入口
output: { }, //输出出口
module: {
loaders: [
{
test:/\.js[x]?$/,
loader: 'babel-loader',
exclude:/node_modules/,
query:{presets: ['es2015','react']}
},
]
},
plugins: [ ],//编译的时候所执行的插件数组
devtool : "source-map" //调试模式
};
开发服务器安装
Webpack
开发服务器需要单独安装,同样是通过npm
进行:
1 | $ sudo npm install -g webpack-dev-server |
可以使用webpack-dev-server
直接启动,也可以增加参数来获取更多的功能,
具体配置可以参见官方文档。在终端输入:
1 | $ webpack-dev-server |
然后打开:http://localhost:8080/webpack-dev-server/index.html
在webpack.config.js
里配置:
1 | //使用webpack-dev-server,提高开发效率 |
webpack.config.js
配置:
1 | var BrowserSyncPlugin = require('browser-sync-webpack-plugin'); |
实例二:react+webpack+es6
开发模式
具体项目地址请移步:https://github.com/hawx1993/react-webpack-demos