webpack了解
目录
1. 通过一个小练习来了解webpack
2. webpack.config.js文件的配置说明
webpack 是一个JavaScript应用程序的静态模块打包器 。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图 (dependency graph) ,其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle 。
四个核心概念:
1. 入口 (entry)
入口会指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。
2. 输出 (output)
output 属性会告诉 webpack 在哪里输出它创建的 bundles ,以及如何命名这些文件,默认值为 ./dist
3. loader
复杂转化浏览器不能识别的文件
4. 插件 (plugins)
扩展Webpack,打包优化、压缩、定义环境变量等。
前言:webpack的作用(为什么产生webpack)
目前简化Web前端开发复杂度,有如下几个方法:
1. 模块化
使代码逻辑清晰,也便于后期管理维护文件。
2. TypeScript
对JavaScript进行拓展后的开发语言:拥有一些JavaScript没有的特性。
TypeScript文件可以被转换为JavaScript文件以使浏览器能识别。
3. Scss、less等
引入样式复用机制,并对CSS进行了扩展(增加了 变量、嵌套、混入、继承、内置函数等特性)。
Scss文件可以被转换为CSS文件以使浏览器能识别。
这其中,TypeScript、Scss等文件需要转换才能让浏览器识别。
如果手动转换,这个处理过程非常繁琐,而WebPack解决了这个问题。
WebPack会找到项目中所有JavaScript模块、Scss文件、TypeScript文件(等浏览器不能直接运行的拓展语言),并使用loader将这些文件转换为合适格式(浏览器可识别)。
webpack安装
安装webpack之前需要安装npm,详情见npm篇
方式一(全局安装)
npm install -g webpack
方式二(局部安装,安装到项目根目录:cd 项目根路径,然后安装)
npm install --save-dev webpack
本地安装的webpack进行打包时使用:node_modules/.bin/webpack 。。。
全局安装的webpack进行打包时使用:webpack 。。。
1. 通过一个小练习来了解 webpack
首先: 创建临时文件夹,并cd进去
第1步. 创建npm说明文件 package.json
npm init
填写 项目的名称、描述、作者等信息
注意:
1. 项目名必须全小写
2. 输入完成一项后回车输入下一项
生成的package.json文件
{
"name": "ee",
"version": "1.0.0",
"description": "hello",
"main": "main.js",
"scripts": {
"test": "testTT"
},
"author": "cx",
"license": "ISC"
}
注意:
1. JSON文件不支持注释
- 本地安装webpack,并作为依赖包
// 安装Webpack
npm install --save-dev webpack
会自动修改package.json文件的devDependencies字段内容
{
"name": "ee",
"version": "1.0.0",
"description": "hello",
"main": "main.js",
"scripts": {
"test": "testTT"
},
"author": "cx",
"license": "ISC",
"devDependencies": {
"webpack": "^4.41.5"
}
}
- 创建相关文件
在项目根目录(package.json所在目录)下创建
public文件夹(存放供浏览器读取的文件)
index.html文件
src文件夹(存放原始文件)
main.js文件
hello.js文件
index.html
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>hello world</title>
</head>
<body>
<div id='root'>
</div>
<script src="bundle.js"></script> <!--引入打包后的js文件-->
</body>
</html>
hello.js
module.exports = function() {
var helloEle = document.createElement('div');
helloEle.textContent = "Hello world!";
return helloEle;
};
main.js
// main.js
const hello = require('./hello.js');
document.querySelector("#root").appendChild(hello());
- 使用webpack进行打包
webpack {entry file} {destination for bundled file}
# {extry file} 填写入口文件的相对路径,本文中就是上述main.js的路径,
# {destination for bundled file}处填写打包文件的存放相对路径
# 填写路径的时候不用添加{}
// 本地安装的webpack则使用 node_modules/.bin/webpack命令
webpack ./src/main.js -o ./public/bundle.js --mode development
如果没有报错信息,会在public文件夹下生成bundle.js文件,这时用浏览器打开index.html
index.html
~~~~~~~以上这些复杂操作都是在终端中进行,不方便且易错~~~~~~~
如下:一步一步通过配置文件,避免在终端执行复杂操作
- 通过配置文件使用webpack
项目根目录下创建 webpack.config.js
module.exports = {
entry: __dirname + "/src/main.js", // 入口文件(唯一)
output: {
path: __dirname + "/public", // 输出文件路径(打包后的文件存放的地方)
filename: "bundle.js", // 输出文件名(打包后输出文件的文件名)
}
}
注意:
1. “__dirname”是node.js中的一个全局变量,它指向当前执行脚本所在的目录
删掉bundle.js,使用如下命令重新生成bundle.js(可以看到:简洁方便了一点)
webpack --mode development
进一步通过配置文件,简化终端操作
- 修改package.json的scripts 更快捷的生成bundle.js
package.json
{
"name": "ee",
"version": "1.0.0",
"description": "hello",
"main": "main.js",
"scripts": {
"test": "testTT",
"start": "webpack --mode development",
"build": "webpack --mode production"
},
"author": "cx",
"license": "ISC",
"devDependencies": {
"webpack": "^4.41.5",
"webpack-cli": "^3.3.10"
}
}
注意:
1. npm的start脚本名比较特殊,在命令行中使用npm start就可以执行其对应的命令。如果是其他名称,则需要用 npm run script_name
删掉bundle.js,使用如下命令重新生成bundle.js (可以看到:更简洁方便了一点)
npm start
2. webpack.config.js 配置说明
- devtool选项 (生成 Source Maps 文件)
通过打包后的文件不容易找到代码出错的地方,Source Maps可以解决这个问题。
devtool选项 | 效果(从上往下:打包速度速度越快,负面作用越多) |
---|---|
source-map | 生成一个单独、完整、功能完全的的文件。具有最好的source map,但会减慢打包速度 |
cheap-module-source-map | 生成一个单独、不带列映射的文件。提高了打包速度,但使得浏览器开发者工具只能对应到具体的行,不能对应到具体的列,不便调试 |
eval-source-map | 生成一个干净完整的文件(使用eval打包源文件模块)。可以在不影响构建速度的前提下生成完整的sourcemap,但是对打包后输出的JS文件的执行具有性能和安全的隐患。在开发阶段使用极好,绝不能用在生产阶段(推荐:中小型项目开发时使用) |
cheap-module-eval-source-map | 最快的生成source map文件,生成的Source Map 会和打包后的JavaScript文件同行显示,没有列映射,和eval-source-map选项缺点一样(推荐:大型项目开发时节约时间使用) |
修改webpack.config.js文件,添加devtool
module.exports = {
devtool: 'eval-source-map',
entry: __dirname + "/src/main.js", // 入口文件(唯一)
output: {
path: __dirname + "/public", // 输出文件路径(打包后的文件存放的地方)
filename: "bundle.js", // 输出文件名(打包后输出文件的文件名)
}
}
- devserver选项(构建本地服务器)
可以实现修改本地文件后,浏览器自动更新内容。
devserver的配置选项 | 说明 |
---|---|
contentBase | 本地服务器路径默认为根文件夹,为其他目录下的文件提供本地服务器则在这里设置 |
port | 设置监听端口,默认为 8080 |
inline | 设置为true,当源文件内容改变时会自动刷新页面 |
historyApiFallback | 在开发单页应用时非常有用,它依赖于HTML5 history API,如果设置为true,所有的跳转将指向index.html |
1. 安装devserver依赖
npm install --save-dev webpack-dev-server
2. 修改webpack.config.js文件,添加devServer
module.exports = {
devtool: 'eval-source-map',
entry: __dirname + "/src/main.js", // 入口文件(唯一)
output: {
path: __dirname + "/public", // 输出文件路径(打包后的文件存放的地方)
filename: "bundle.js", // 输出文件名(打包后输出文件的文件名)
},
devServer: {
contentBase: "./public", // 本地服务器所加载的页面所在目录
historyApiFallback: true, // 不跳转
inline: true, // 实时刷新
}
}
3. 修改package.json文件的scripts,添加server
{
"name": "ee",
"version": "1.0.0",
"description": "hello",
"main": "main.js",
"scripts": {
"test": "testTT",
"start": "webpack --mode development",
"server": "webpack-dev-server --open"
},
"author": "cx",
"license": "ISC",
"devDependencies": {
"webpack": "^4.41.5",
"webpack-cli": "^3.3.10"
}
}
4. 运行
npm run server
5. 修改hello.js内容,浏览器会自动更新
- loader
通过不同的loader,webpack可以调用外部脚本或工具对不同格式的文件进行转换。
scss转为css。
下一代的JS文件(ES6,ES7)转换为现代浏览器兼容的JS文件。
React则可以将JSX文件转为JS文件。
Loaders需要单独安装并在webpack.config.js中的modules关键字下进行配置:
字段 | 说明 |
---|---|
test | 用以匹配loaders所处理文件的拓展名的正则表达式(必须) |
loader | loader的名称(必须) |
include/exclude | 手动添加必须处理的文件/文件夹 或 屏蔽不需要处理的文件/文件夹(可选) |
query | 为loaders提供额外的设置选项(可选) |
例1
src文件夹下创建hello.json文件,内容如下:
{
"hello":"hello hi cx"
}
修改hello.js文件,修改后内容如下:
var helloFile = require('./hello.json');
module.exports = function() {
var helloEle = document.createElement('div');
helloEle.textContent = helloFile.hello;
return helloEle;
};
由于webpack3.*/webpack2.*已经内置可处理JSON文件,无需再添加webpack1.*需要的json-loader。
npm start 即可查看效果
3.1 Babel
将不被浏览器支持的最新JavaScript代码(ES6、ES7等)、基于JavaScript进行拓展的语言(如Typescript、React的JSX)转换为浏览器支持的代码(可识别)。
本质:
由多个模块化的包组成(babel-core 核心npm包、babel-loader、babel-preset-env、babel-preset-react )。针对相应功能使用对应的包。
最常用的是解析Es6的babel-env-preset包、解析JSX的babel-preset-react包。
通过如下2步,就可以允许使用ES6以及JSX的语法了。
1. 安装devserver依赖
// npm一次性安装多个依赖模块,用空格隔开
npm install --save-dev babel-core babel-loader babel-preset-env babel-preset-react
2. 修改webpack.config.js文件,添加module
module.exports = {
devtool: 'eval-source-map',
entry: __dirname + "/src/main.js", // 入口文件(唯一)
output: {
path: __dirname + "/public", // 输出文件路径(打包后的文件存放的地方)
filename: "bundle.js", // 输出文件名(打包后输出文件的文件名)
},
devServer: {
contentBase: "./public", // 本地服务器所加载的页面所在目录
historyApiFallback: true, // 不跳转
inline: true, // 实时刷新
},
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader",
options: {
presets: [
"env", "react"
]
}
},
exclude: /node_modules/
}
]
}
}
例1
npm install --save react react-dom
修改hello.js文件(ES6语法)如下:
import React, {Component} from 'react'
import helloConfig from './hello.json';
class Hello extends Component{
render() {
return (
<div>
{helloConfig.hello}
</div>
);
}
}
export default Hello
修改main.js文件
import React from 'react';
import {render} from 'react-dom';
import Hello from './Hello';
render(<Hello/>, document.getElementById('root'));
npm start 查看效果,效果正常则说明react和es6被正常打包
注意babel的版本会导致打包出错
解决:
回退8以下低版本 npm install -D babel-loader@7 babel-core babel-preset-env
// "@babel/react","@babel/env" ,高版本babel-loader
// "env", "react", 低版本
~~~~~~~将babel配置分离 提出到单独文件中(名为".babelrc"的文件)~~~~~~~
修改webpack.config.js文件如下:
module.exports = {
devtool: 'eval-source-map',
entry: __dirname + "/src/main.js", // 入口文件(唯一)
output: {
path: __dirname + "/public", // 输出文件路径(打包后的文件存放的地方)
filename: "bundle.js", // 输出文件名(打包后输出文件的文件名)
},
devServer: {
contentBase: "./public", // 本地服务器所加载的页面所在目录
historyApiFallback: true, // 不跳转
inline: true, // 实时刷新
},
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader",
},
exclude: /node_modules/
}
]
}
}
新建.babelrc文件(mac下需要在终端通过:vim .babelrc)如下
{
"presets": ["react", "env"]
}
3.2 CSS
webpack提供了css-loader 和 style-loader ,二者组合在一起来处理样式表,并嵌入到webpack打包后的JS文件中。
css-loader:可使用类似@import 和 url(...)的方法实现 require()的功能。
style-loader:将计算后的样式加入页面中。
1. 安装devserver依赖
npm install --save-dev style-loader css-loader
2. 修改webpack.config.js文件如下:
module.exports = {
devtool: 'eval-source-map',
entry: __dirname + "/src/main.js", // 入口文件(唯一)
output: {
path: __dirname + "/public", // 输出文件路径(打包后的文件存放的地方)
filename: "bundle.js", // 输出文件名(打包后输出文件的文件名)
},
devServer: {
contentBase: "./public", // 本地服务器所加载的页面所在目录
historyApiFallback: true, // 不跳转
inline: true, // 实时刷新
},
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader",
},
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
{
loader: "style-loader"
},
{
loader: "css-loader",
}
]
}
]
}
}
src文件夹下 新建main.css 文件
/**
* main.css
*/
body {
line-height: 1.42857;
color:#404040;
background-color:#fef;
font-family: -apple-system,BlinkMacSystemFont,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",Helvetica,Arial,sans-serif;
}
main.js中引入main.css
import './main.css';
npm run server 查看效果
css会和js打包到同一个文件中,并不会打包为一个单独的css文件。将css打包为单独的文件需要进行额外的配置
~~~~~~~CSS modules:所有的类名、动画名默认都只作用于当前模块~~~~~~~
修改webpack.config.js的如下部分
{
test: /\.css$/,
use: [
{
loader: "style-loader"
},
{
loader: "css-loader",
options: {
modules: true, // 指定是否启用css modules
localIdentName: '[name]---[local]---[hash:base64:5]' // 指定css的类名格式 (样式文件名 样式名 随机hash值)
}
}
]
}
如果打包报错,解决(css-loader3.0以上版本)
options: {
modules:{
localIdentName: '[name]_[local]-[hash:base64:5]' // 指定css的类名格式
}
}
新建 hello.css
.hello {
padding: 20px;
background-color: #ffe;
border: 2px solid #bce;
}
修改hello.js文件如下:
import React, {Component} from 'react'
import helloConfig from './hello.json';
import helloCss from './hello.css';
class Hello extends Component{
render() {
return (
<div className={helloCss.hello}>
{helloConfig.hello}
</div>
);
}
}
export default Hello
npm run server
效果图
~~~~~~~其他预处理css Loader~~~~~~~
Less Loader
Sass Loader
Stylus Loader
Postcss Loader
将不存在于CSS中的特性(Sass和Less等 对css扩展后的特性)转化为浏览器可识别的CSS语句
- plugin
plugins(插件): 用来拓展Webpack功能
内置插件、三方插件
第一步:npm安装(三方插件时需要,内置插件不需要)
第二步:webpack.config.js配置plugin
4.1 banner-plugin (内置) 可用来:添加版权声明
修改webpack.config.js,添加plugin
const webpack = require('webpack');
module.exports = {
devtool: 'eval-source-map',
entry: __dirname + "/src/main.js", // 入口文件(唯一)
output: {
path: __dirname + "/public", // 输出文件路径(打包后的文件存放的地方)
filename: "bundle.js", // 输出文件名(打包后输出文件的文件名)
},
devServer: {
contentBase: "./public", // 本地服务器所加载的页面所在目录
historyApiFallback: true, // 不跳转
inline: true, // 实时刷新
},
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader",
},
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
{
loader: "style-loader"
},
{
loader: "css-loader",
options: {
modules:{
localIdentName: '[name]_[local]-[hash:base64:5]' // 指定css的类名格式
}
}
}
]
}
]
},
plugins: [
new webpack.BannerPlugin('cx版权所有,翻版必究')
]
}
打包后 bundle.js第一行会新增:/*! cx版权所有,翻版必究 */
/*! cx版权所有,翻版必究 */
/******/ (function(modules) { // webpackBootstrap
4.2 html-webpack-plugin (三方)
根据一个index.html模板,生成一个自动引用打包后的JS文件的新index.html文件
用于:每次生成的js文件名不同时使用(hash值)
前处理
1. 清空public文件夹下文件
2. 在src文件夹下新建index.tmpl.html模版文件,内容如下:
<!-- index.tmpl.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>hello world</title>
</head>
<body>
<div id='root'>
</div>
</body>
</html>
第一步:安装plugin
npm install --save-dev html-webpack-plugin
第二步:配置plugin
修改webpack.config.js,添加plugin
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
devtool: 'eval-source-map',
entry: __dirname + "/src/main.js", // 入口文件(唯一)
output: {
path: __dirname + "/public", // 输出文件路径(打包后的文件存放的地方)
filename: "bundle.js", // 输出文件名(打包后输出文件的文件名)
},
devServer: {
contentBase: "./public", // 本地服务器所加载的页面所在目录
historyApiFallback: true, // 不跳转
inline: true,
},
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader",
},
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
{
loader: "style-loader"
},
{
loader: "css-loader",
options: {
modules:{
localIdentName: '[name]_[local]-[hash:base64:5]' // 指定css的类名格式
}
}
}
]
}
]
},
plugins: [
new webpack.BannerPlugin('cx版权所有,翻版必究'),
new HtmlWebpackPlugin({
template: __dirname + "/src/index.tmpl.html"
})
]
}
3. 发布阶段
发布产品前,还需要对打包的文件进行额外的处理
1. 压缩
2. 优化
3. 缓存
分离CSS和JS
- 压缩
新建webpack.production.config.js文件,内容如下
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
devtool: 'null', // 发布阶段 置null:减少打包文件大小
entry: __dirname + "/src/main.js", // 入口文件(唯一)
output: {
path: __dirname + "/public", // 输出文件路径(打包后的文件存放的地方)
filename: "bundle.js", // 输出文件名(打包后输出文件的文件名)
},
devServer: {
contentBase: "./public", // 本地服务器所加载的页面所在目录
historyApiFallback: true, // 不跳转
inline: true,
hot: true,
},
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader",
},
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
{
loader: "style-loader"
},
{
loader: "css-loader",
options: {
modules:{
localIdentName: '[name]_[local]-[hash:base64:5]' // 指定css的类名格式
}
}
}
]
}
]
},
plugins: [
new webpack.BannerPlugin('cx版权所有,翻版必究'),
new HtmlWebpackPlugin({
template: __dirname + "/src/index.tmpl.html"
}),
]
}
修改package.json文件,scripts中添加build脚本如下
{
"name": "ee",
"version": "1.0.0",
"description": "hello",
"main": "main.js",
"scripts": {
"test": "testTT",
"start": "webpack --mode development",
"server": "webpack-dev-server --open",
"build": "NODE_ENV=production webpack --config ./webpack.production.config.js --progress"
},
"author": "cx",
"license": "ISC",
"devDependencies": {
"babel-core": "^6.26.3",
"babel-loader": "^7.1.5",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"css-loader": "^3.4.2",
"html-webpack-plugin": "^3.2.0",
"style-loader": "^1.1.3",
"webpack": "^4.41.5",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.10.1"
},
"dependencies": {
"react": "^16.12.0",
"react-dom": "^16.12.0"
}
}
npm start 打包的bundle.js大小为3.4M
npm run build 打包的bundle.js大小为137k
- 优化
OccurenceOrderPlugin (内置)
为组件分配ID,通过这个插件webpack可以分析和优先考虑使用最多的模块,并为它们分配最小的ID
UglifyJsPlugin(内置)
压缩JS代码(webpack4中,废弃了webpack.optimize.UglifyJsPlugin用法)
ExtractTextPlugin (三方)
分离CSS和JS文件 (extract-text-webpack-plugin还不能支持webpack4.0.0以上的版本)
第一步:安装ExtractTextPlugin
npm install --save-dev extract-text-webpack-plugin@next
第二步:修改webpack.production.config.js文件
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
devtool: 'null', // 减少打包文件大小
entry: __dirname + "/src/main.js", // 入口文件(唯一)
output: {
path: __dirname + "/public", // 输出文件路径(打包后的文件存放的地方)
filename: "bundle.js", // 输出文件名(打包后输出文件的文件名)
},
devServer: {
contentBase: "./public", // 本地服务器所加载的页面所在目录
historyApiFallback: true, // 不跳转
inline: true,
hot: true,
},
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader",
},
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
{
loader: "style-loader"
},
{
loader: "css-loader",
options: {
modules:{
localIdentName: '[name]_[local]-[hash:base64:5]' // 指定css的类名格式
}
}
}
]
}
]
},
plugins: [
new webpack.BannerPlugin('cx版权所有,翻版必究'),
new HtmlWebpackPlugin({
template: __dirname + "/src/index.tmpl.html"
}),
new webpack.optimize.OccurrenceOrderPlugin(),
new ExtractTextPlugin("style.css")
],
optimization: {
minimizer: [
new UglifyJsPlugin({
uglifyOptions: {
compress: false,
}
})
]
}
}
- 缓存
修改webpack.production.config.js文件的输出文件名为随机值
filename: "hello__[hash].js", // 输出文件名(打包后输出文件的文件名)
run start 生成js文件名如下:
hello__6d19f918d84b53822a27.js
修改代码,再次run start 会生成新的js打包文件:
hello__788ebf2443fe4b5d4097.js
clean-webpack-plugin (清除之前残余的js文件)
cnpm install clean-webpack-plugin --save-dev
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
new CleanWebpackPlugin()
npm run build 可以看到之前的js打包文件都被清除了