webpack基础
认识webpack
webpack是一个现代的JavaScript应用的静态模块打包工具。
涉及到两个概念:模块 和 打包
模块
通过模块化开发完成项目后,还需要处理模块间的各种依赖,并且将其进行整合打包。而webpack其中一个核心就是让我们可能进行模块化开发,并且会帮助我们处理模块间的依赖关系。而且不仅仅是JavaScript文件,我们的CSS、图片、json文件等等在webpack中都可以被当做模块来使用。这就是webpack中模块化的概念。
打包(bundle)
就是将webpack中的各种资源模块进行打包合并成一个或多个包(Bundle)。并且在打包的过程中,还可以对资源进行处理,比如压缩图片,将scss
转成css
,将ES6
语法转成ES5
语法,将TypeScript
转成JavaScript
等等操作。
但打包的操作似乎grunt/gulp也可以帮助我们完成,它们有什么不同呢?
和grunt/gulp的对比
grunt/gulp的核心是Task
我们可以配置一系列的task
,并且定义task
要处理的事务(例如ES6、ts转化,图片压缩,scss转成css)。之后让grunt/gulp
来依次执行这些task
,而且让整个流程自动化。所以grunt/gulp也被称为前端自动化任务管理工具。
来看一个gulp的task:下面的task就是将src下面的所有js文件转成ES5的语法。并且最终输出到dist文件夹中。
const gu1p = requireC('gulp');
const babe1 = require('gu1p-babe1');
gulp.task('js',()=>
gulp.src('src/*.js')
.pipe(babe1({
presets: ['es2015']
}))
.pipe(gulp. dest('dist'))
);
什么时候用grunt/gulp呢?
如果你的工程模块依赖非常简单,甚至是没有用到模块化的概念。只需要进行简单的合并、压缩,就使用grunt/gulp即可。但是如果整个项目使用了模块化管理,而且相互依赖非常强,我们就可以使用更加强大的webpack了。
所以,grunt/gulp和webpack有什么不同呢?
-
grunt/gulp
更加强调的是前端流程的自动化,模块化不是它的核心。 -
webpack
更加强调模块化开发管理,而文件压缩合并、预处理等功能,是他附带的功能。
webpack的安装
安装webpack首先需要安装Node.js,Node.js自带了软件包管理工具npm。
cnpm install webpack --save-dev
//--save-dev是开发时依赖,项目打包后不需要继续使用的。
在终端直接执行webpack命令,使用的全局安装的webpack。当在package.json中定义了scripts时,其中包含了webpack命令,那么使用的是局部webpack。
webpack的起步
文件和文件夹解析:
dist文件夹:用于存放之后打包的文件
src文件夹:用于存放我们写的源文件
-----main.js项目的入口 文件。具体内容查看wb下面详情。
index.html:浏览器打开展示的首页html
package.json:通过npm init生成的,npm包管理的文件(暂时没有用上,后面才会用上)
js文件的打包
webpack src/main.js dist/bundle.js
使用打包后的文件
打包后会在dist文件下,生成一个bundle.js文件。bundle.js文件,是webpack处理了项目直接文件依赖后生成的一个js文件,我们只需要将这个js文件在index.html中引入即可
webpack的配置
局部安装webpack
因为一个项目往往依赖特定的webpack版本,全局的版本可能很这个项目的webpack版本不一致,导出打包出现问题。所以通常一个项目,都有自己局部的webpack。
//安装指定自己需要的版本
cnpm install webpack@3.6.0 --save-dev
//启动webpack打包
node_modules/.bin/webpack
package.json中定义启动
我们可以在package.json的scripts中定义自己的执行脚本。
{
"name": "meetwebpack",
"version": "1.0.0",
"description":"",
"main": 'index.js',
"scripts": {
"build": "'webpack"
},
"author":"",
"license":"ISC",
"devDependencies": {
"webpack": "^3. 6. 0"
}
}
package.json中的scripts的脚本在执行时,会按照一定的顺序寻找命令对应的位置。
首先,会寻找本地的node_modules/.bin路径中对应的命令。如果没有找到,会去全局的环境变量中寻找。
//执行build指令
cnpm run build
搭建本地服务器
webpack提供了一个可选的本地开发服务器,这个本地服务器基于node.js搭建,内部使用express框架,可以实现我们想要的让浏览器自动刷新显示我们修改后的结果。
npm install --save-dev webpack-dev-server@2.9.1
devserver
也是作为webpack中的一个选项,选项本身可以设置如下属性:
`contentBase`:为哪一个文件夹提供本地服务,默认是根文件夹,我们这里要填写./dist
`port`:端口号
`inline`:页面实时刷新
`historyApiFallback`:在SPA页面中,依赖HTML5的history模式
devServer: {
contentBase: './dist',
inline: true
}
可以再配置另外一个scripts:--open参数表示直接打开浏览器
"scripts": {
"build": "webpack",
"dev": "webpack-dev-server --open"
},
入口和出口
如果每次使用webpack的命令都需要写上入口和出口作为参数,有一种方法可以将这两个参数写到配置中,在运行时,直接读取。
创建一个webpack.config.js文件
const path = require('path')
module.exports = {
//入口:可以是字符串/数组/对象,这里我们入口只有一个,所以写一个字符串即可
entry:'./src/main.js' ,
//出口:通常是一个对象,里面至少包含两个重要属性,path 和filename
output:{
path: path. resolve(__dirname, 'dist'), //注意: path通常是一 个绝对路径
filename: 'bundle.js'
}
}
loader的使用
什么是loader?
loader是webpack中一个非常核心的概念。在开发中我们不仅仅有基本的js代码处理,我们也需要加载css、图片,也包括一些高级的将ES6转成ES5代码,将TypeScript转成ES5代码,将scss、less转成css,将.jsx、.vue文件转成js文件等等。对于webpack本身的能力来说,对于这些转化是不支持的。此时需要给webpack扩展对应的loader。
loader使用过程:
步骤一:通过npm安装需要使用的loader
步骤二:在webpack.config.js中的modules关键字下进行配置
大部分loader我们都可以在webpack的官网中找到,并且学习对应的用法。loader 的执行顺序是从下到上,从右到左。
css文件处理
需要用到两个loader:css-loader 和 style-loader。css-loader
负责加载css文件;style-loader
负责将css具体样式嵌入到文档中。
安装
cnpm install --save-dev css-loader style-loader
webpack.config.js中配置
const path = require('path');
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module:{
rules:[
{
test:/\.css$/,
use:['style-loader','css-loader'],
}
]
},
}
因为webpack在读取使用的loader的过程中,是按照从右向左的顺序读取的。所以style-loader
需要放在css-loader
的前面。
less/scss文件处理
需要安装less 和 less-loader。
rules: [{
test: /\.less$/,
use: [{
loader: "style-loader" // creates style nodes from JS strings
}, {
loader: "css-loader" // translates CSS into CommonJS
}, {
loader: "less-loader" // compiles Less to CSS
}]
}]
图片文件处理
url-loader、file-loader
当加载的图片, 小于limit
时, 会将图片编译成base64
字符串形式.
当加载的图片, 大于limit
时, 需要使用file-loader
模块进行加载.
修改文件名称
当图片大于limit时,使用file-loader
默认会将该图片打包重新命名为32位的hash值放到dist文件夹下。
{
test: /\.(png|jpg|gif|jpeg)$/,
use: [
{
loader: 'url-loader',
options: {
// 当加载的图片, 小于limit时, 会将图片编译成base64字符串形式.
// 当加载的图片, 大于limit时, 需要使用file-loader模块进行加载.
limit: 13000,
// [ext]:表示原始文件名的后缀。[name]:表示原始文件名。[hash:8]:为了防止图片名称冲突,依然使用hash,但是我们只保留8位
name: 'img/[name].[hash:8].[ext]'
},
}
]
},
我们发现图片并没有显示出来,这是因为图片使用的路径不正确。默认情况下,webpack会将生成的路径直接返回给使用者但是,我们整个程序是打包在dist文件夹下的,所以这里我们需要在路径下再添加一个dist/
。
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: 'dist/'
},
babel 的使用
如果希望将ES6的语法转成ES5,那么就需要使用babel。而在webpack中,我们直接使用babel对应的loader就可以了。
npm install --save-dev babel-loader@7 babel-core babel-preset-es2015
配置webpack.config.js文件
{
test: /\.js$/,
// exclude: 排除
// include: 包含
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['es2015']
}
}
}
plugin的使用
plugin
是插件的意思,通常是用于对某个现有的架构进行扩展。webpack中的插件,就是对webpack现有功能的各种扩展,比如打包优化,文件压缩等等。
loader和plugin区别
loader
主要用于转换某些类型的模块,它是一个转换器。plugin
是插件,它是对webpack本身的扩展,是一个扩展器。
plugin的使用过程:
步骤一:通过npm安装需要使用的plugins(某些webpack已经内置的插件不需要安装)
步骤二:在webpack.config.js中的plugins中配置插件。
添加版权的Plugin
BannerPlugin
,属于webpack自带的插件。
const path = require('path')
const webpack = require('webpack')
module.exports = {
...
plugins: [
new webpack.BannerPlugin('最终版权归aaa所有'),
],
}
打包html的plugin
在真实发布项目时,需要将index.html文件打包到dist文件夹中,这个时候就可以使用 HtmlWebpackPlugin
插件。HtmlWebpackPlugin
插件可以为我们做这些事情:自动生成一个index.html文件(可以指定模板来生成)将打包的js文件,自动通过script标签插入到body中。
npm install html-webpack-plugin --save-dev
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
...
plugins: [
new HtmlWebpackPlugin({
template: 'index.html'//指定生成Html文件时使用的模板。
}),
],
}
另外,我们需要删除之前在output
中添加的publicPath
属性,否则插入的script标签中的src可能会有问题。
CleanWebpackPlugin
CleanWebpackPlugin 会在打包之前删除指定目录下的内容。
npm install clean-webpack-plugin -D
webpack.config.js
const {CleanWebpackPlugin} = require('clean-webpack-plugin'); //注意加{}
module.exports = {
...
plugins:[
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new CleanWebpackPlugin() //不能加参数。
],
}
性能优化
优化构建速度
优化babel-loader
- 优化Loader的文件搜索范围
- 把Babel编译过的文件缓存起来
module.exports = {
module:{
rules:[
{
test:/\.js$/,//js文件才使用babel
loader:'babel-loader?cacheDirectory=ture',//下次只需要编译更改过的代码文件即可
include:[resolve('src')],//只在src文件夹下查找
exclude:/node_modules/,//不会去查找的路径
}
]
}
}
ParallelUgifyPlugin
使用ParallelUglifyPlugin开启多进程压缩JS文件
happyPack
让loader
以多进程去处理文件,借助缓存机制,可以在rebuild
的时候更快
const path = require('path')
const HappyPack = require('happypack');
module.exports = {
...
new HappyPack({
id: 'jsx',
threads: 4,
loaders: [ 'babel-loader' ]
}),
new HappyPack({
id: 'styles',
threads: 2,
loaders: [ 'style-loader', 'css-loader', 'less-loader' ]
})
}
DllPlugin和DllReferencePlugin
DllPlugin可以将特定的类库提前打包然后引入。这种方式可以极大的减少打包类库的次数,只有当类库更新版本才有需要重新打包,并且也实现了将公共代码抽离成单独文件的优化方案。webpack 自带的库。使用DllPlugin减少基础模块编译次数。使用DllReferencePluhin将依赖文件引入项目中。
const path = require('path')
const webpack = require('webpack')
module.exports = {
...
new webpack.DllPlugin({
name:'[name]-[hash]',//name必须和output.library一致
context:__dirname,//该属性需要与DllReferencePlugin中一致
path:path.join(__dirname,'dist','[name]-mainfest.json')
}),
new webpack.DllReferencePlugin({
context: __dirname,
mainfest:require('./dist/vendor-mainfest.json')
})
}
IgnorePlugin
noParse
优化开发体验
使用自动刷新
- Webpack监听文件
- 启动webpack时加上--watch参数;
- 在配置文件中设置watch:true。
- DevServer刷新浏览器
DevServer刷新浏览器有两种方式:
- 向网页中注入代理客户端代码,通过客户端发起刷新
- 向网页装入一个iframe,通过刷新iframe实现刷新效果
热更新
在文件发生改变的时候,不需要重启服务器即可生效。
开启方式:
webpack-dev-server --hot
- 使用
HotModuleReplacementPlugin
const path = require('path')
const webpack = require('webpack')
module.exports = {
...
new webpack.HotModuleReplacementPlugin()
}
优化产出代码
- 使用生产环境
- 小图片base64编码
- bundle 加 hash
- 使用CDN
- 提取公共改代码
- 懒加载
webpack中配置Vue
el和template区别
在前面的Vue实例中,我们定义了el属性,用于和index.html中的#app进行绑定,让Vue实例之后可以管理它其中的内容。
这里,我们可以将div元素中的{{message}}内容删掉,只保留一个基本的id为div的元素
但是如果我依然希望在其中显示{{message}}的内容,应该怎么处理呢?
我们可以再定义一个template属性,代码如下:
new Vue({
el: '#app',
template: '<div id="app">{{message}}</div>',
data: {
message:'coderwhy'
}
})
el和template模板的关系是什么呢?
el用于指定Vue要管理的DOM,可以帮助解析其中的指令、事件监听等等。
而如果Vue实例中同时指定了template,那么template模板的内容会替换掉挂载的对应el的模板。这样做之后我们就不需要在以后的开发中再次操作index.html,只需要在template中写入对应的标签即可
稍后会将template
模板中的内容进行抽离。会分成三部分书写:template
、script
、style
,结构变得非常清晰。
.vue文件封装处理
main.js
//使用Vue进行开发
import Vue from 'vue'
import App from './vue/App.vue'
new Vue({
el: '#app',
template: '<App/>',
components: {
App
}
})
vue-->App.vue
<template>
<div>
<h2 class="title">{{message}}</h2>
<h2>{{name}}</h2>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
message: 'Hello Webpack',
name: '张三'
}
},
}
</script>
<style scoped>
.title {
color: green;
}
</style>
安装vue、vue-loader 和 vue-template-compiler 进行处理。
npm install vue-loader vue-template-compiler --save-dev
cnpm install vue --save
module: {
rules: [
{
test: /\.vue$/,
use: ['vue-loader']
}
]
},
//解决runtime-only版本的Vue报错
resolve: {
// alias: 别名
extensions: ['.js', '.css', '.vue'],
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。