什么是webpack
webpack是模块打包机,是前端资源模块化管理和打包工具。可以将许多松散的模块按照依赖和规则打包成符合生产环节部署的前端资源。它做的事情就是,分析你的项目结构,找到js模块以及其他的一些浏览器不能直接运行的拓展语言(Sass,typeScript等),并将其打包为适合的格式供浏览器使用。把各种资源 JS、JSX、less、sass 图片都当做模块来处理。
webpack的工作方式:把你的项目当做一个整体,通过一个给定的主文件(index.js)webpack 将从这个文件开始找到你的项目的所有依赖文件,使用loaders处理它们。最后打包成一个可识别的js文件。
webpack是以commonJs的形式书写脚本的,但对AMD/CMD的支持也很全面。
webpack并不强制你使用某种模块化方案,而是通过兼容所有模块化方案让你无痛接入项目,当然这也是webpack牛逼的地方。
有了webpack,你可以随意选择你喜欢的模块化方案,至于怎么处理模块之间的依赖关系及如何按需打包,放轻松,webpack会帮你处理好的
开始使用webpack
安装:
npm是随同NodeJs一起安装的包管理工具,是Node的模块管理器。nodejs已经集成了npm所以我们安装nodejs的时候,就已经安装了npm
npm常用的场景:
- 允许用户从npm服务器下载别人编写的第三方包到本地。
- 允许用户从npm服务器下载并安装别人编写的命令行程序到本地使用。
- 允许用户将自己编写的包或命令行程序上传到npm服务器供别人使用。
用npm安装的模块会在工程目录node_modules目录中,因此代码只需要通过 require('express') 方式就好。
package.json位于模块目录下,用于定义包的属性。
npm 菜鸟课程
阮一峰npm安装
- npm安装好后我们创建一个空文件夹当做是我们的项目
- 在空文件夹中安装webpack npm install -g webpack
- 空文件夹中创建一个package.json,这是一个标准的npm说明文件,里面说明了当前项目的依赖模块,自定义脚本等等。npm init 可以自动创建这个package.json文件。输入这个命令之后,终端会问一系列例如项目名称,项目描述,作者信息等信息,如果不考虑发布到npm服务器上,直接回车默认就好。
package.json文件就绪。
回到之前的空文件夹,并在里面创建两个文件夹,app文件夹和public文件夹,app文件夹用来存放原始数据和我们将写的JavaScript模块,public文件夹用来存放准备给浏览器读取的数据(包括使用webpack生成的打包后的js文件以及一个index.html文件)。在这里还需要创建三个文件,index.html 文件放在public文件夹中,两个js文件(Greeter.js和main.js)放在app文件夹中,此时项目结构如下图所示
index.html
文件只有最基础的html代码,它唯一的目的就是加载打包后的js文件 技术 (bundle.js)webpack只需要指定一个入口文件,将自动识别项目所依赖的其他文件,
通过配置来使用webpack.我们建一个webpack.config.js的文件,用来说明webpack的配置告诉 webpack 它需要做什么。
module.exports = {
entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件
output: {
path: __dirname + "/public",//打包后的文件存放的地方
filename: "bundle.js"//打包后输出文件的文件名
}
}
“__dirname”是Node.js中的一个全局变量,它指向当前执行脚本所在的目录。
现在如果你需要打包文件只需要在终端里你运行webpack(非全局安装需使用node_modules/.bin/webpack)命令就可以了,这条命令会自动参考webpack.config.js文件中的配置选项打包你的项目
有没有想过如果可以连webpack(非全局安装需使用node_modules/.bin/webpack)这条命令都可以不用呢?当然可以。值得庆幸的是npm可以引导任务执行,对其进行配置后可以使用简单的npm start命令来代替这些繁琐的命令。在package.json中对npm的脚本部分进行相关设置即可
npm的start是一个特殊的脚本名称,它的特殊性表现在,在命令行中使用npm
start就可以执行相关命令,如果对应的此脚本名称不是start,想要在命令行中运行时,需要这样用npm run {script name}如npm run build
但是为什么引入webpack打包的项目里 为什么可以使用 require,module.exprot,__dirname这些nodejs拥有的变量或属性。我在蜂巢项目中某个组件的render函数中打印如下。页面可以正常显示,并且都能打印出相应的值
render() {
console.log('buf:', buf)
}
因为webpack是node模块,所以webpack的解析编译的上下文环境都是node ,webpack默认会打包node 的一些原生模块,如果webpack打包的是node端的代码,需要设置target: 'node' 因为输出代码的运行环境是 Node.js,源码中依赖的 Node.js 原生模块没必要打包进去。
使用webpack构建本地服务器
想不想让你的浏览器检测你修改了代码,并且自动刷新修改后的结果呢。webpack提供一个可选的恩地开发服务器。这个服务器基于NodeJs构建,可以实现想要的这些功能,不过它是一个单独的组件,在webpack中进行配置之前需要单独安装下它
npm install --save-dev webpack-dev-server
开发时候你需要运行你的应用,就需要一个server, webpack-dev-server 主要是启动了一个使用 express 的 Http服务器 。它的作用 主要是用来伺服资源文件 。此外这个 Http服务器 和 client 使用了 websocket 通讯协议,原始文件作出改动后, webpack-dev-server会实时的编译, webpack-dev-server 其实就是封装了 express 的一个server,里面又使用了一个中间件 webpack-dev-middleware 来把webpack编译的文件放在内存中,请求资源时候都是从内存中取,编译过程也是在内存中运行,所以速度非常快。最后的编译的文件并没有输出到目标文件夹,实时编译后的文件都保存到了内存当中.
进行npm run build命令是将打包后的代码保存到磁盘当中,就是电脑某个盘某个盘中可见。不然就是保存在内存当中。
开发时候这几个文件存在内存中。不需要build。
devServer:{
contentBase: '', //静态资源的目录 相对路径,相对于当前路径 默认为当前config所在的目录
devtool: 'eval',
hot: true, //自动刷新
inline: true,
port: 3005
},
存在内存中的意思为,直接内
web-dev-server
起的服务器的根目录下。例如起的服务端口为http://localhost:8181,打包输入的文件名称为build.js。则http://localhost:8181/build.js。就可以直接访问打包的js。上面需要设置contentBase
,才能正常访问,这时候一般contentBase
的html
都会有一句<script src="bundle.js"></script>
。我们知道HtmlWebpackPlugin
插件可以自动生成html并且会把输出的js自动引用好。我们可以不写contentBase
。而是写
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html', // web-dev-server默认自动访问index.html
template: './example/index.html', // 模板 就是contentBase里的html去掉<script src="bundle.js"></script>`
inject: true
}),
]
如果使用 web-dev-server
,HtmlWebpackPlugin
这个插件生成的html
默认也是输出到跟路径。所以我们启动服务后。就可以看到正常的页面显示了。
现在开发一般用的都是es6.需要通过bable转译
babel 转码es6后的结果
babel使用 import
因此 Babel 的做法实际上是将不被支持的 import 翻译成目前已被支持的 require
Loaders
打包成多个vendor
打包vendor
因为vendor.js是不怎么变动的,我们可以把vendor.js进行缓存,我们部署到服务器的时候可以对vendor.js设置长达一年的 Cache Control。或者采取cdn等方法进行缓存。但是在项目里实际使用中发现,每当我们更改了业务代码并重新进行项目构建生成 app.js 时,vendor.js 的 hash 值也会随之改变,上面提到的缓存策略也就失去了意义。可以使用
经过各种方法解决hash值变化的问题
发现HashedModuleIdsPlugin插件 比较好用
打包优化
new webpack.DefinePlugin
某些情况,我们需要在页面中输出开发调试内容,但是又不想让这些调试内容在发布的时候泄露出去,那么我们可以采用魔力变量(magic globals)来处理。
配置文件:
业务逻辑代码中写入
按照下面的代码写入,我们就可以在我们自己设定的环境下进行更具针对性的调试。比如我们希望在开发环境下可以AJAX可以调试本地mock数据,然后在发布的时候,可以正常访问服务端数据。那么通过此种方式可以完全实现。
这个插件就相当于定义了两个全局变量
__DEV__
和__PRERELEASE__
。
常用插件:
- OccurenceOrderPlugin :为组件分配ID,通过这个插件webpack可以分析和优先考虑使用最多的模块,并为它们分配最小的ID
- UglifyJsPlugin:压缩JS代码;
- ExtractTextPlugin:分离CSS和JS文件
webpack打包文件过大优化方案
webpack2 中文文档
阮一峰 webpack demo
单页打包优化
在开发一个单页面应用的时候,我们通常会将应用的JavaScript代码打包成两个文件:一个用于存放内容很少更改的第三方依赖库,这部分代码的体积一般会比较大;另一个存放更改比较频繁的业务逻辑代码,但它的体积一般比第三方依赖库小。为了方便描述,我们可以分别称这两个文件为vendor.js与app.js。
目前这样是一个打包入口。打包成一个app.js把所有的代码都打包在一起。网上有说按照页面分成多个入口文件打包然后再每个页面通过引入相对应的打包文件。进行优化我觉得这样不好webpack解惑:多入口文件打包策略
借助Code Splitting按需加载
webpack的整体风格是All in one 就是将从入口文件开始的所有引用到的模块都打包到一个js文件(包括css 、图片,这里只说js)。在打包的过程中会有一系列的名称替换,细心的朋友会发现我们的模块被命名为更加简单,节省空间的数字了。不同于requireJS可以直接require线上文件的方式。webpack推荐将所有可能用到的文件(模块)都打包。但是这样做仅适合使用率较高的小体积模块。对于体积大有不常用的模块,反而显得不适合。如果你尝试过在webpack工程中直接require线上url。运行的时候会抛出一个异常——不能找到模块,那是因为对于打包后的js中默认是在这个包内查找模块,或者说webpack工程中的require函数默认是在包(文件)内找,压根儿就没打算发起http请求。所以就不要想直接require线上的url了。
拿到webpack工程就只能打包在一起,搞出一个超大的文件,把页面卡住?webpack官方给出了解决方案——Code Splitting。这种方式并不代表webpack有意投靠AMD,而是另辟蹊径弥补了自身的短板。简单来说,还是All in one 还是打包成一个包,还是在包内部找模块,只是这一个包不一定只有一个文件。我们直译Code Splitting为“代码拆分”,也就是说我们可以理解为将all in one的包在拆成多个包,当需要引用的时候再引用下载、加载,只是这种加载是通过webpack内部机制发起http请求实现的。我们将摸个不常用的大体积模块从包中分离出来,当包内的语句引用到了这个模块后,webpack会判断这个模块是被分离出去的,应当发起http请求拉取。不是不能拉向上模块,只是只拉和自己有渊源的模块。webpack 使用require.ensure作为切割点。commonJs建议使用require.ensure异步加载相关内容。Es6提案建议引入import()函数,完成动态加载。所以我们的vue路由中就是const Base = r => require.ensure([], () => r(require('./Base.vue')), 'base');
方式进行异步加载的
vue异步组件
es6 import()动态加载
webpack Code Splitting
不需要手动引入app.js
最近使用vue-cli构建项目。发现index.html里不需要手动的引入打包好的app.js。而是提示
<body>
<div id="app"></div>
<!-- built files will be auto injected --> // 自动会被引入。
</body>
因为这个插件html-webpack-plugin
new HtmlWebpackPlugin({
template: path.join(__dirname, 'src/index.tmpl.html'),
filename: 'index.html'
})
打包只会打包js,我们不可能每次都去手动复制一个index.html到打包好的dist文件中,并且我们在很多情况下,生产环境和开发环境不同,导致index.html引入的资源路径不同。template是模板,我们可以创建一个模板,它指定编译时我们copy的index.html文件。filename是最终生成的文件名。所以使用这个插件之后会可以神奇的看到在你的 build 文件夹会生成一个 index.html 文件和一个 bundle.js 文件,而且 index.html 文件中自动引用 webpack 生成的 bundle.js 文件。它会自动帮你生成一个 html 文件,并且引用相关的 assets 文件(如 css, js)。
dist文件夹
执行npm run build
后会生成一个dist的文件夹。里面存放着打包之后的各种文件。这个功能其实和gulp/grunt自动化工具一样。根据配置自动进行文件创建合并压缩等功能。所以index.html自动引入打包好的js或者css也是插件执行构建的结果。