webpack 入门
基本安装
新建项目文件夹、初始化 npm 以及在本地安装 webpack
mkdir webpack-demo && cd webpack-demo
npm init -y
npm install webpack webpack-cli --save-dev
新建目录
webpack-demo
+ |- /dist
+ |- index.html
+ |- /src
+ |- index.js
|- package.json
以下提到的文件,如果没有特殊说明(如 ‘src/index.js’),则默认在根目录下。
dist/index.html
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Webpack Sample Project</title>
</head>
<body>
<div id='app'>
</div>
<script src="bundle.js"></script>
</body>
</html>
在 index.html 中将 bundle.js 引入。
src/index.js
document.getElementById('app').innerHTML = 'hello webpack';
新建 webpack 配置文件
touch webpack.config.js
当前目录结构:
webpack-demo
|- /dist
|- index.html
|- /src
|- index.js
|- package.json
+ |- webpack.config.js
下文中的所有配置,如未经特殊说明,均是在 webpack.config.js 文件中进行配置
设置 entry & output
webpack.config.js
const path = require('path');
module.exports = {
+ entry: {
+ main: './src/index',
+ },
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ filename: 'bundle.js'
+ }
}
entry 是应用程序入口起点。程序从这里启动执行。
output 指定在哪里输出打包好的文件。path 是输出文件的路径,filename 是输出文件名。
path.resolve 是 nodejs 的一个方法,用于将路径转化为绝对路径。
__dirname 是 nodejs 的一个全局变量,表示当前文件的绝对路径值。
打包文件
使用 打包命令 打包一次文件
webpack --config webpack.config.js --mode development
上面这条命令指定了 webpack 配置文件为 webpack.config.js,环境为 development(开发)
然而,如果每次都输入这么长一串命令来执行打包,实在不方便,所以我们将这条命令写入 npm 脚本
package.json
{
"name": "webpack-demo",
"version": "1.0.0",
"description": "",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
+ "build": "webpack --config webpack.config.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^4.0.1",
"webpack-cli": "^2.0.9",
}
}
现在,我们可以通过 ‘npm run build’ 来执行打包命令。
下面这些命令行选项我们应该知道
webpack – building for development
webpack -p – building for production (minification)
webpack –watch – for continuous incremental building
webpack -d – including source maps
webpack –colors – making building output pretty
source map
开发离不开调试,通过调试我们能快速定位 bug 及时修复。当 webpack 打包源代码时,可能会很难追踪到错误和警告在源代码中的原始位置。
为了更容易地追踪错误和警告,JavaScript 提供了 source map 功能,将编译后的代码映射回原始源代码。
它允许我们设置通过何种方式编译源码,使得编译后的代码可读性更高,便于调试。
webpack.config.js
const path = require('path');
module.exports = {
entry: {
main: './src/index',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
+ devtool: 'eval-source-map'
}
现在,我们就能在浏览器控制台 ‘Sources’ 下看到我们的源码了。
loader
loader 用于对模块的源代码进行转换。loader 可以使你在 import 或”加载”模块时预处理文件。
CSS-loader
我们可以使用 loader 告诉 webpack 如何加载 CSS 文件,这里我们用到 style-loader 和 css-loader
webpack 允许我们在 js 文件中使用 css 文件,使用 css-loader 预处理 css 文件,通过 style-loader 将 <style> 标签插入 HTML。
npm install --save-dev style-loader css-loader
webpack.config.js
const path = require('path');
module: {
entry: {
main: './src/index',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
devtool: 'eval-source-map',
+ module: {
+ rules: [{
+ test: /\.css$/,
+ use: ['style-loader','css-loader']
+ }]
+ }
}
webpack 根据正则表达式,来确定应该查找哪些文件,并将其提供给指定的 loader。在这种情况下,以 .css 结尾的全部文件,都将被提供给 style-loader 和 css-loader。
我们使用 import ‘./style.css’ 引入样式后,该 CSS 文件将被作为内部样式插入到 html 文件的
中。我们尝试一下,在项目中添加一个 style.css 文件,并将其导入我们的 index.html 中
main.css
.hello {
color: red;
}
index.js
import './main.css'
document.getElementById('app').innerHTML = 'hello webpack';
function component() {
var element = document.createElement('div');
element.innerHTML = '<h1>hello</h1>';
element.classList.add('hello');
return element;
}
document.body.appendChild(component());
现在打包一下程序,webpack 会在 index.html 中插入一个内部样式。
<head>
<style type="text/css">.hello {
color:red;
}</style>
</head>
CSS Modules
想象这样一个场景:我们在两个页面中各有一个模块都作 “title” 使用,但是我们想给他们设置不同的样式。假如我们都把它们的 class 都设置为 “title”,那么就会造成命名空间污染,我们必须要为它们设置不同的 className。有两个还好办,假如有 5 个、10 个呢?要起多少个不同的名字呢?
CSS Modules 就是来解决这个问题的,它可以为每一个 JS 文件创造局部作用域,来避免全局 CSS 类名重复问题。意味着,我们可以在不同的 JS 文件中使用相同的类名。
CSS Modules 基于 css-loader 插件,修改配置文件
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
main: './src/index.js' // 程序入口
},
output: {
path: path.resolve(__dirname, 'dist'), // 输出文件目录
filename: 'bundle.js' // 输出文件
},
devtool: 'inline-source-map',
module: {
rules: [{
test: /\.css$/,
- use: ['style-loader', 'css-loader']
+ use: [{
+ loader: 'style-loader'
+ }, {
+ loader: 'css-loader',
+ options: {
+ modules: true,
+ importLoaders: 1,
+ localIdentName: '[path][name]---[local]---[hash:base64:5]' // 指定css的类名格式
+ }
+ }]
}]
}
}
localIdentName 允许我们自己指定编译后的类名格式。
[path]:当前路径
[name]:当前 CSS 文件名
[local]:当前局部作用域类名
[hash:bash64:5]:编码格式
现在我们修改一下代码。
我们添加一个 hello.js 组件,将 main.css 引入
hello.js
import styles from './main.css'
export default {
component() {
var element = document.createElement('div');
element.innerHTML = `<h1 class="${styles.hello}">hello component</h1>`;
return element;
}
}
在 index.js 中使用 hello.js 组件。
index.js
import hello from './hello';
document.getElementById('app').innerHTML = 'hello webpack';
document.body.appendChild(hello.component());
现在可以看到 hello.js 组件中的 <h1> 的 class 变为了 “src-main—hello—3rVhM”,正是我们定义的格式。
关于 CSS Modules,阮一峰老师有一篇教程讲的很详细:CSS Modules 用法教程。
plugins
插件(plugins)是 webpack 的支柱功能,它们会在整个构建过程中生效,执行相关的任务。插件目的在于解决 loader 无法实现的其他事。
HtmlWebpackPlugin
HtmlWebpackPlugin 插件用于生成一个自动引用我们打包后的 JS 文件 html 文件,简单来说就是生成一个 html 文件,该 html 文件会自动将打包后的 JS 文件插入其中。
安装插件
npm install --save-dev html-webpack-plugin
webpack.config.js
const path = require('path');
+ const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
devtool: 'eval-source-map',
entry: {
app: './src/index.js',
print: './src/print.js'
},
module: {
rules: [{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}]
},
+ plugins: [
+ new HtmlWebpackPlugin({
+ filename: 'index.html',
+ template: path.join(__dirname, 'src/index.html')
+ })
+ ],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
filename 是输出文件名。
template 是指定一个 html 模板,生成的 html 文件以 template 指定文件为模板。
更多配置
MiniCssExtractPlugin
到目前为止,我们的 CSS 样式还是通过 <style> 标签放在 <head> 下的,假如我们想把每一个 CSS 文件分离出来独立引用,该怎么做呢?需要借助 MiniCssExtractPlugin 这个插件。
安装
npm install --save-dev mini-css-extract-plugin
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
+ const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
entry: {
main: './src/index.js' // 程序入口
},
output: {
path: path.resolve(__dirname, 'dist'), // 输出文件目录
filename: 'bundle.js' // 输出文件
},
devtool: 'inline-source-map',
module: {
rules: [{
test: /\.css$/,
use: [{
- loader: 'style-loader'
- }, {
+ loader: MiniCssExtractPlugin.loader,
+ options: {
+ publicPath: './'
+ }
}, {
loader: 'css-loader',
options: {
modules: true, // 指定启动 css modules
importLoaders: 1,
localIdentName: '[path][name]---[local]---[hash:base64:5]' // 指定css的类名格式
}
}]
}]
},
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: path.join(__dirname, 'src/index.html')
}),
+ new MiniCssExtractPlugin({
+ filename: "[name].css"
+ })
]
}
现在,我们可以看到,CSS 文件被作为 <link> 独立引进来了。
webpack-dev-server
假如我们每次代码发生变化时,都需要手动执行 ‘npm run build’的话,实在是太麻烦了。
我们希望我们的代码发生变化时,可以被自动实时编译。这就可以使用 webpack-dev-server。
安装
npm install --save-dev webpack-dev-server
修改配置文件
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
app: './src/index.js',
print: './src/print.js'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
},
devtool: 'eval-source-map',
module: {
rules: [{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}]
},
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: path.join(__dirname, 'src/index.html')
})
],
+ devServer: {
+ contentBase: './dist'
+ host: 'localhost', // 默认是localhost
+ port: 3000, // 端口
+ open: true, // 自动打开浏览器
+ }
};
contentBase:默认webpack dev server是从项目的根目录提供服务,如果要从不同的目录提供服务,可以通过contentBase来配置,比如可以把contentBase配置成’./public’。
在 package.json 中配置一条命令
package.json
{
"name": "webpack-react-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "test",
"dev": "webpack --config webpack.config.js --mode development",
+ "server": "webpack-dev-server --open"
},
"author": "jaakko",
"license": "ISC",
"dependencies": {},
"devDependencies": {
"css-loader": "^2.0.0",
"html-webpack-plugin": "^3.2.0",
"style-loader": "^0.23.1",
"webpack": "^4.27.1",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.10"
}
}
现在,运行 ‘npm run server’ 就可以看到效果:当我们的源码发生变化时,web 服务器就会自动重新加载编译后的代码。
注意!!!
在配置文件发生变化时,还是需要重新运行程序,否则配置不会生效
除了 webpack-dev-server,还有另外两种方式,可以帮助我们在代码发生变化后自动编译代码。
查看
模块热替换
模块热替换(Hot Module Replacement 或 HMR),它允许在运行时更新各种模块,而无需进行完全刷新。
启动热替换比较简单,只需要引入一个 webpack 内置插件,更新 webpack-dev-server 设置
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
+ const webpack = require('webpack');
module.exports = {
entry: {
main: './src/index.js' // 程序入口
},
output: {
path: path.resolve(__dirname, 'dist'), // 输出文件目录
filename: 'bundle.js' // 输出文件
},
devtool: 'inline-source-map',
module: {
rules: [{
test: /\.css$/,
use: [
// {
// loader: 'style-loader'
// },
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../'
}
}, {
loader: 'css-loader',
options: {
modules: true, // 指定启动 css modules
importLoaders: 1,
localIdentName: '[path][name]---[local]---[hash:base64:5]' // 指定css的类名格式
}
}
]
}]
},
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: path.join(__dirname, 'src/index.html')
}),
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css"
}),
+ new webpack.HotModuleReplacementPlugin() // 热更新
],
devServer: {
contentBase: './dist',
host: 'localhost', // 默认是localhost
port: 3000, // 端口
open: true, // 自动打开浏览器
+ hot: true // 开启热更新
}
}
此时已经完成了配置,但实际上并不会生效,我们还需要在入口文件加入以下
index.js
if (module.hot) {
// 实现热更新
module.hot.accept();
}
这里有一个小问题,我再设置了热更新后修改 css 文件不会自动更新模块,具体原因还未知,还在寻找中…
至此,webpack 的基本配置就完成了。更多配置待探索…