随着前端工程越来越复杂,单独依靠几个文件来写业务代码已经无法保证项目的可维护性了,所以我们将不同业务逻辑拆分成不同的模块,然后去分开引入这些模块,每个模块做自己的事情,这样就能保障项目的可维护性和扩展性了,但假如有几千个模块,这就需要一个工具来帮助们管理这些模块了,可以说webpack就是帮助我们管理复杂项目的一个工具,它可以帮助我们实现代码转换、文件优化、代码分割、模块合并、自动刷新、代码效验等功能。
本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个捆绑包。
entry指定webpack从哪个模块开始构建内部依赖图、进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。我们可以通过在 webpack 配置中配置 entry 属性,来指定一个入口起点(或多个入口起点)。
module.exports = {
//单入口写法
entry: './src/index.js'
};
module.exports = {
//多入口写法/对象写法
entry: {
login: './src/login.js',
index: './src/index.js'
}
};
output 属性告诉 webpack 在哪里输出它所创建的 bundles文件,以及如何命名这些文件,默认值为 ./dist。基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。你可以通过在配置中指定一个 output 字段,来配置这些处理过程。
output: {
//出口
filename: 'bundle.js', //打包后的文件名
path: path.resolve(__dirname, 'build') //打包出的文件所放位置,路径必须是绝对路径,(__dirname:当前目录下产出一个dist目录)
publicPath: '/' //默认'/', 指定资源文件引用的目录(如图片等),对于打包路径不会有任何影响,
},
在编译时不知道最终输出文件的 publicPath 的情况下,publicPath 可以留空,并且在入口起点文件运行时动态设置。如果你在编译时不知道 publicPath,你可以先忽略它,并且在入口起点设置 。
__webpack_public_path__ = 'myPublicPath'
//.src/index.js
如果配置创建了多个单独的 “chunk”(例如,使用多个入口起点或使用像 CommonsChunkPlugin 这样的插件),则应该使用占位符(substitutions)来确保每个文件具有唯一的名称。
output: {
filename: '[name].js', //会跟随入口名字进行命名
path: path.resolve(__dirname, 'build')
}
loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。
本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块,在 webpack 的配置中 loader 有两个目标:test 属性:用于标识出应该被对应的 loader 进行转换的某个或某些文件,use 属性:表示进行转换时,应该使用哪个 loader。
module.exports = {
module: {
rules: [
{
test: /\.css$/, use: 'css-loader' },
{
test: /\.ts$/, use: 'ts-loader' }
]
}
}
loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。
使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作符来创建它的一个实例。
let HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports={
plugins: [ //数组 放着所有的webpack插件
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
}),
],
}
npm init -y //默认创建
//或
npm init //可在创建时自行定义配置
创建后目录结构如下:(此处node_modules忽略,安装依赖时会自动创建)
注意:
config目录:此处我用来存放webpack配置文件。
script目录:start.js这是我用来书写项目运行所需代码,build.js项目打包所需代码。
(1)public/index.html: 存放html模板
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>
<div id="root">div>
body>
html>
(2)src/index.js:项目入口文件
import React from 'react';
import ReactDom from 'react-dom';
ReactDom.render(<div>这是组件</div>, document.getElementById('root'));
–save-dev:(简写-D)将依赖写入package.js文件devDependencies模块,为开发环境所需依赖。
–save: (简写-S)生产环境所需依赖,为产品发布后不可少的依赖,将依赖写入package.js文件devDependencies模块。
webpack: 模块打包工具.
webpack-cli : webpack脚手架
webpack-dev-server:webpack官网出的一个小型express服务器,主要特性是支持热加载。
npm install webpack webpack-cli webpack-dev-server --save-dev
react: 安装react
react-dom:安装React Dom,这个包是用来处理virtual DOM(虚拟dom)
npm install react react-dom --save
安装babel,将es6转换成es5语法,部分浏览器可能不识别部分es6语法。
@babel/core 与 babel-core的区别:官方7.0 之后,包名升级为 @babel/core,个人理解、babel-core表示7.0以下版本,所有babel7.0版本以上相关统一前缀为@babel/。
@babel/core:babel 核心包,编译器,提供转换的API。
@babel/preset-react:Babel可以转换JSX语法
@babel/plugin-proposal-class-properties:解析类的属性
@babel/plugin-proposal-decorators:解析装饰器
@babel/plugin-transform-runtime:将 helper 和 polyfill 都改为从一个统一的地方引入,并且引入的对象和全局变量是完全隔离的,可以提高代码重用性,缩小编译后的代码体积
@babel/preset-env:对es2015, es2016. es2017的支持
npm install @babel/core @babel/preset-react @babel/preset-env --save--dev
npm install @babel/plugin-transform-runtime @babel/plugin-proposal-decorators --save--dev
npm install @babel/plugin-proposal-class-properties --save--dev
webpack-dev-server有两种启动方式:webpack命令启动、node.jsAPI启动。这里首先介绍直接使用webpack命令启动。
打开config目录下webpack.config.js文件
注意:需要安装所用到的loader和plugin
npm i babel-loader style-loader css-loader html-webpack-plugin mini-css-extract-plugin --save-dev
如果需要使用ip地址打开需要安装address模块
npm i address --save-dev
写入以下内容,这里
const path = require('path'); //内置模块,可以将相对路径解析成绝对路径
const HtmlWebpackPlugin = require('html-webpack-plugin'); //生成html文件
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); //抽离样式插件link引入
const address = require('address');
const srcRoot = path.resolve(__dirname, '../src');
module.exports = {
entry: srcRoot + '/index.js', //入口文件
output: {
path: path.resolve(__dirname, '../build'), //打包出的文件所放位置,路径必须是绝对路径,(__dirname:以当前目录下产出一个build目录)
filename: 'index.js',
},
devServer: {
port: 8182,
contentBase: path.join(__dirname, '../build'),
host: address.ip(), //这里我使用了ip地址打开,不配置的话默认localhost
compress: true,
},
module: {
//多个loader需要[],loader还可以写成对象{loader:""},执行顺序从右向左执行,从下到上执行 //
rules: [
{
test: /\.js/,
exclude: /node_modules/, //不包含node_modules目录下的js文件
use: {
loader: 'babel-loader',
},
},
{
test: /\.css/,
use: ['style-loader', 'css-loader'],
},
],
},
plugins: [
new HtmlWebpackPlugin({
title: 'react-app',
template: path.resolve(__dirname, '../public/index.html'), //本地模板文件的位置
filename: 'index.html',
minify: false,
inject: true, // 1、true或者body:所有JavaScript资源插入到body元素的底部;head: 所有JavaScript资源插入到head元素中;false:所有静态资源css和JavaScript都不会注入到模板文件中
}),
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: 'css/[name].css',
}),
],
};
{
"presets": ["@babel/preset-react", "@babel/preset-env"],
"plugins": [
[
"@babel/plugin-proposal-decorators", //支持装饰器插件
{
"legacy": true //使用旧式(第一阶段)的装饰器语法和行为
}
],
[
"@babel/plugin-proposal-class-properties", //编译类
{
"loose": true //将编译类属性以使用赋值表达式而不是Object.defineProperty
}
],
["@babel/plugin-transform-runtime"]
]
}
到此处就已经可以启动我们的项目了,这里使用命令执行
注意 :这里我的webpack.config文件放在了根目录下的config目录下,所以需要添加:–config ./config/webpack.config.js,不写的话一般默认是根目录
webpack-dev-server --open --mode development --config ./config/webpack.config.js --watch
可以将命令写入package.js文件中配置自定义命令
控制台输入启动命令:npm run start
如果出现webpack-cli/bin/config-yargs报错,可能是webpack-cli版本不兼容的问题,可以尝试更换webpack-cli的版本。
例如安装指定版本:
npm i [email protected] --save-dev
经常我们在项目中一般使用less或sass来替代css文件,这里只介绍less配置方法
安装相关loader:
npm i less postcss-loader less-loader --save-dev
在webpack.config.js文件中配置以下代码:
{
test: /\.less$/,
exclude: /node_modules/,
include: [/src/],
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
importLoaders: 2,
modules: {
localIdentName: '[path]_[local]_[hash:base64:6]', //给css类名添加哈希值,避免组件中命名重复而导致样式混合
},
},
},
'postcss-loader', //考虑浏览器的兼容性,添加浏览器前缀
{
loader: 'less-loader',
},
],
}
(1)去掉webpack.config.js文件中devServer模块。
(2)打开script目录下的start.js文件,写入:
const WebpackServer = require('webpack-dev-server');
const baseConfig = require('../config/webpack.config');
const webpack = require('webpack');
const address = require('address'); //用来获取当前计算机的IP,MAC和DNS服务器。
const compiler = webpack(baseConfig);
let port = 8081;
const devServer = new WebpackServer(compiler, {
host: address.ip(),
compress: true, //启用 gzip 压缩。
index: 'index.html', //启动索引html文件,默认index.html
hot: true, //是否启用热替换
clientLogLevel: 'none', //启用内联模式(inline mode),会在控制台打印消息,用none阻止。
inline: true, //dev-server 的两种不同模式之间切换:true内联模式(inline mode)、 false: iframe 模式,默认true。
open: true, //自动打开浏览器
});
devServer.listen(port);
(3)在package.js文件中修改
控制台执行npm run start命令,可以看到使用node命令服务启动成功。
在项目实际开发中,如果端口号写死,一旦当前端口号被占用,将无法启动服务,需要手动修改配置文件端口号,为了开发方便,我们可以在配置文件中通过引入net模块来检测端口占用情况,从而实现动态修改端口。
修改script目录下的start.js文件并安装net、chalk模块(chalk模块可忽略,引入这个模块可以支持日志颜色的修改,利于我们更快找到日志)
npm i net chalk --save-dev
start.js文件内容:
const WebpackServer = require('webpack-dev-server');
const baseConfig = require('../config/webpack.config');
const webpack = require('webpack');
const chalk = require('chalk'); //支持日志颜色修改
const net = require('net');
const address = require('address'); //用来获取当前计算机的IP,MAC和DNS服务器。
const compiler = webpack(baseConfig);
let port = 8081;
function listenPort() {
const server = net.createServer().listen(port);
server.on('listening', () => {
server.close();
startDevServer();
});
server.on('error', (e) => {
if (e.code === 'EADDRINUSE') {
++port;
console.log(chalk.yellow('端口号被占用,修改端口号为' + port));
listenPort();
}
});
}
function startDevServer() {
const devServer = new WebpackServer(compiler, {
host: address.ip(),
compress: true, //启用 gzip 压缩。
index: 'index.html', //启动索引html文件,默认index.html
hot: true, //是否启用热替换
clientLogLevel: 'none', //启用内联模式(inline mode),会在控制台打印消息,用none阻止。
inline: true, //dev-server 的两种不同模式之间切换:true内联模式(inline mode)、 false: iframe 模式,默认true。
open: true, //自动打开浏览器
});
devServer.listen(port);
}
listenPort();
打开script目录下的build.js文件,
安装依赖:(不需要ora模块时,可忽略,此模块主要用来显示当前加载状态,出现编译中图标)
npm i ora --save-dev
const webpack = require('webpack');
const chalk = require('chalk'); //支持日志颜色修改
const baseConfig = require('../config/webpack.config');
const ora = require('ora');
const spinner = ora('building>>>>').start();
const compiler = webpack({
...baseConfig,
mode: 'production'
});
compiler.run((err, stats) => {
if (err || stats.hasErrors()) {
console.log(chalk.red('build fail:' + stats.hasErrors()));
return;
}
spinner.stop();
console.log(chalk.green('build success'));
});