0
点赞
收藏
分享

微信扫一扫

开发工具 —— 以实战案例方式初识 webpack 4.x 前端工程管理工具

愚鱼看书说故事 2022-03-30 阅读 8

webpack 基本简介

webpack 按官网文档的说法:是一个 JavaScript 应用程序的 静态模块打包器(module bundler),针对一个入口文件,webpack 会递归地构建一个依赖关系图,最终将这些依赖的模块打包成一个或多个 bundler,开发者需要做的就是在配置文件中 写好配置,然后剩下的工作 webpack 会帮开发者自动处理

在这里插入图片描述

webpack 的诞生历史,很有意思,听一听下面这个现实故事就了解到 webpack 是怎么诞生的~

Tobias Koppers 是 Webpack 仓库 创建者,简单讲就是 webpack 是 Tobias 的儿子,他是一个德国人,也是一个 java 开发者,却从没有写过 web 页面,有意思的是正是一个没有写过 web 页面的人却发明了当代 web 开发的基石

在 java 里面有个很出名的技术 叫 GWT(Google Web Toolkit), GWT 是把 java 代码转换成 javascript ,也就是让后端来写前端,本质上也是在 AST 层面对代码做一成转换,babel 也是干这件事的,但是 GWT 这门技术没有流行起来,后面 Google 也不推广了

说白了,java 一直想染指 web 页面的,以前 java 有 Applet,主要处理页面,动画,后来 Flash 打败了 Applet,接着 javascript又取代了FLash,但人类欲望是无止境的,现在搞 web vr,大型设计软件,javascript 的性能是满足不了的,所有出现了 WebAssembly

Tobias 在使用 GWT 里面有个 feature「code splitting」,就是他给 node.js 库 modules-webmake 提了一个 issue,希望在 node.js 内实现 feature「code splitting」,但 modules-webmake 一直没有实现这个功能。于是,在 2012 年 3 月 10 号,Tobias 在 github 上开了一个新的项目 webpack,其一开始的功能主要是 Bundle 代码

webpack 核心概念

  • 入口(entry):指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始
  • 输出(output):告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist
  • 转换(loader):让 webpack 能够去处理那些非 JavaScript 文件(样式、图片等)
  • 插件(plugins):用于执行范围更广的任务
  • 模式(mode):使用相应模式的配置:development 本地模式、production 线上模式(压缩)

【操作步骤】

> 在任意盘下创建一个文件夹,并命名为 webp-demo,执行 cmd 后执行如下指令 "F:\wwwroot\webp-demo>"
> npm init -y													# 用于初始一个项目 package.json 配置文件
> npm install webpack@4.x webpack-cli@3.x --save-dev			# 安装 webpack 相关依赖
> md src build src\js src\css src\imgs
> 在 src/js/index.js、src/css/index.css、src/index.html 相应的路径创建相关文件
> 在根目录下创建 webpack.config.js 文件

【代码编辑】

# src/index.html 文件编码如下,引入了打包后的 JS 文件 dist/js/index.js
# 设置 webpack.config.js 配置文件的入口和输出口,配置 package.json 项目配置
> npm run build
> webp-demo@1.0.0 build F:\wwwroot\webp-demo
> webpack

asset js/build.js 1.2 KiB [emitted] (name: main)  
./src/js/index.js 1 bytes [built] [code generated]
webpack 5.24.4 compiled successfully in 65 ms
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>首页</title>
    <script src="../dist/js/index.js"></script>
</head>
<body>
</body>
</html>

【配置信息】

const { resolve } = require('path');			// 引入 path 处理打包文件相对路径

module.exports = {
    entry: './src/js/index.js',					// 打包前的 JS 文件
    output: {
        filename: 'js/index.js',				// 打包后的 JS 文件
        path: resolve(__dirname, 'dist')		// 打包后的根目录
    },
    module: {
        rules: []
    },
    plugins: [],
    mode: 'development'
}
{
    ......
        
    "scripts": {
        "build": "webpack --progress --colors",
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    
    ......
}

【编译结果】

打包后的目录结构如下,这个时候通过浏览器打开 src/index.html 文件发现什么都没有 …

|-- dist						# 打包之后的目录
	|-- js
		|-- index.js
|-- node_modules
|-- src							# 打包之前的目录
	|-- css
	|-- imgs
	|-- js
		|-- index.js
	|-- index.html
|-- package-lock.json
|-- package.json
|-- webpack.config.js

给 src/js/index.js 编辑如下,然后执行 npm run build 后再刷新 src/index.html ,看到 H1 号字体的 “hello webpack” 内容

document.write('<h1>hello webpack</h1>');

webpack 页面配置

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>首页</title>
    <script src="../dist/js/index.js"></script>
</head>
<body>
</body>
</html>

观察 src/index.html 代码可以发现,里面引入了打包后的 JS 脚本文件,那么能不能通过配置自动引用关联 JS 脚本文件呢?

# 安装 html-webpack-plugin 插件用于自动化引入 JS 脚本,配置如下
> npm install --save-dev html-webpack-plugin@4.x 
# 将 src/index.html 内部 JS 引入去除,引入 html-webpack-plugin 插件,调整 webpack-config.js 配置文件
# 执行 npm run build 打包指令后的 dist/index.html 发现自动引入了相关 JS 脚本文件,运行 dist/index.html 效果同样
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>首页</title>
</head>
<body>

</body>
</html>
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    ......
    
    plugins: [
        new HtmlWebpackPlugin({ template: resolve(__dirname, 'src/index.html'), filename: 'index.html' })
    ],
    mode: 'development'
}

webpack 模板引擎

随着前端的发展至今,讲究的是前端的模块化、组件化,这样的开发方式易于后期的项目维护,减少后期维护成本

webpack处理组件中的模板文件有两种方式:

如果需要在模板中引用变量或遍历比较复杂的数组时,使用 JS 模板引擎(例:EJS),而非 HTML 模板

# 在前后端分离的开发模式下,前端必定在模板中涉及到较为复杂的数据渲染,因此选择 ejs-loader 处理 EJS 语法糖文件
# 安装 ejs-loader 插件用于处理 EJS 语法,配置如下
> npm install ejs-loader --save-dev
# 引入 ejs-loader 插件,调整 webpack-config.js 配置文件
# 将 src/index.html 修改为 index.ejs,再执行 npm run build 后,生成了 dist/index.html,效果相同
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    ......
    
    module: {
        rules: [
            { test: /\.ejs/, loader: 'ejs-loader', options: { esModule: false } }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({ template: resolve(__dirname, 'src/index.ejs'), filename: 'index.html' }),
    ],
    mode: 'development'
}

在 src 下创建 about.ejs 文件:创建一个关于我们新的页面,代码如下,并调整配置为一个多页面项目开发

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>关于我们</title>
</head>
<body>

</body>
</html>
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    ......
    
    module: {
        rules: [
            { test: /\.ejs/, loader: 'ejs-loader', options: { esModule: false } },
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({ template: resolve(__dirname, 'src/index.ejs'), filename: 'index.html' }),
    ],
    mode: 'development'
}
  • 执行 npm run build 打包,并生成了对应的 dist/about.html 文件,编辑器打开 dist/about.html 发现同样使用了 dist/js/index.js
  • 很明显 dist/js/index.js 映射的是 dist/index.html,就需要 dist/js/about.js 去映射 dist/about.html
  • 在 src/js 下创建 about.js 并调整配置文件入口和出口配置,并约束 HTML 一一映射一个或多个相应 JS 脚本文件
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: {							// 参考 webpack 文档,多入口配置
        index: './src/js/index.js',
        about: './src/js/about.js',
    },
    output: {
        filename: 'js/[name].js',
        path: resolve(__dirname, 'dist')
    },
    module: {
        rules: [
            { test: /\.ejs/, loader: 'ejs-loader', options: { esModule: false } },
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({ template: resolve(__dirname, 'src/index.ejs'), filename: 'index.html' }),
        new HtmlWebpackPlugin({ template: resolve(__dirname, 'src/about.ejs'), filename: 'about.html' }),
    ],
    mode: 'development'
}

webpack 开发的主旨不只是组件化开发,而且还能够实现工程自动化,若一个项目不只两个页面或着出现几十个界面,如果一个一个的去配置是不理想的。

这里做个项目文件调整,webpack.config.js 是一个配置文件,将每个页面看成是一个路由,在根目录下创建 route.config.js、entry.config.js、plugin.config.js、rule.config.js,通过遍历处理路由 route.config.js 解析得到 entry.config.js、plugin.config.js 配置,并引进 entry.config.js、plugin.config.js、rule.config.js 修改 webpack.config.js 文件

# route.config.js 设计如下,方便后期添加路由即可
------------------------------------------------------------------------------------------------------------
module.exports = [
    { path: 'index', en: 'HOME', title: '首页' },
    { path: 'about', en: 'ABOUT US', title: '关于我们' },
];
------------------------------------------------------------------------------------------------------------
# entry.config.js 设计如下,根据路由配置设置多入口配置
------------------------------------------------------------------------------------------------------------
const { resolve } = require('path');
const routes = require('./route.config');

let entry = {};
routes.map(item => {
    entry[item.path] = resolve(__dirname, './src', `js/${item.path}.js`);
});

module.exports = entry;
------------------------------------------------------------------------------------------------------------
# plugin.config.js 设计如下,根据路由配置设置多界面配置
------------------------------------------------------------------------------------------------------------
const { resolve } = require('path');
const routes = require('./route.config');
const HtmlWebpackPlugin = require('html-webpack-plugin');

let plugins = [];
routes.map(item => {
    plugins.push(
        new HtmlWebpackPlugin({
            template: resolve(__dirname, './src', `${item.path}.ejs`),
            filename: `${item.path}.html`,
            chunks: [item.path], inject: true, path:item.path, minify: true,       // minify 压缩和清空注释
            favicon: resolve(__dirname, './src', 'favicon.ico'),
            title: item.title
        })
    );
});

module.exports = plugins;
------------------------------------------------------------------------------------------------------------
# rule.config.js 设计如下,根据 loader 预处理器设置多界面配置
------------------------------------------------------------------------------------------------------------
module.exports = [
    { test: /\.ejs/, loader: 'ejs-loader', options: { esModule: false } },
];
------------------------------------------------------------------------------------------------------------
const { resolve } = require('path');
const entry = require('./entry.config');
const plugins = require('./plugin.config');
const rules = require('./rule.config');

module.exports = {
    entry,
    output: {
        filename: 'js/[name].js',
        path: resolve(__dirname, './dist/')
    },
    module: {
        rules: [ ...rules ],
    },
    plugins: [ ...plugins ],
    mode: 'development'
}

这样的写法,功能分类,有助于后期项目运维,具有针对性修改对应文件,甚至连 rules 也可以抽离出来配置,后续讲解~

以上配置仅仅适用于所有的界面都只有一级的情况,如果存在多级目录的话就需要调整,例:在 src 下创建 src/about/index.ejs 关于我们详情页面,调整配置

# route.config.js 设计如下,方便后期添加路由即可
------------------------------------------------------------------------------------------------------------
module.exports = [
    { path: 'index', en: 'HOME', title: '首页' },
    { path: 'about', en: 'ABOUT US', title: '关于我们' },
    { path: 'about/index', en: 'ABOUT US', title: '关于我们详情' },
];
------------------------------------------------------------------------------------------------------------
# entry.config.js 设计如下,根据路由配置设置多入口配置
------------------------------------------------------------------------------------------------------------
const { resolve } = require('path');
const routes = require('./route.config');

let entry = {};
routes.map(item => {
    const index = item.path.indexOf('/') > -1 ? item.path.replace(/\//g, '.') : item.path;
    entry[index] = resolve(__dirname, './src', `js/${item.path}.js`);
});

module.exports = entry;
------------------------------------------------------------------------------------------------------------
# plugin.config.js 设计如下,根据路由配置设置多界面配置
------------------------------------------------------------------------------------------------------------
......

let plugins = [];
routes.map(item => {
    const chunk = item.path.indexOf('/') > -1 ? item.path.replace(/\//g, '.') : item.path;
    plugins.push(
        new HtmlWebpackPlugin({
            template: resolve(__dirname, './src', `${item.path}.ejs`),
            filename: `${item.path}.html`,
            chunks: [chunk], inject: true, path:item.path, minify: true,       // minify 压缩和清空注释
            favicon: resolve(__dirname, './src', 'favicon.ico'),
            title: item.title
        })
    );
});

module.exports = plugins;
------------------------------------------------------------------------------------------------------------
# 执行 npm run build 打包的目录结构如下:
|-- src												|-- dist
	|-- about											|-- about
		|-- index.ejs										|-- index.html
	|-- js												|-- js
		|-- index.js										|-- index.js
		|-- about.js										|-- about.js
		|-- about/index.js									|-- about.index.js
	|-- favicon.ico											|-- favicon.ico
	|-- index.ejs										|-- index.html
	|-- about.ejs										|-- about.html
------------------------------------------------------------------------------------------------------------
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>关于我们详情</title>
	<link rel="icon" href="../favicon.ico"><!-- 会自动匹配相对路径 -->
</head>
<body>
<script src="../js/about.index.js"></script>
</body>
</html>

这里需要约束一下的仅仅是配置路由与映射 JS 脚本文件的命名,例:path: ‘about/index’ 映射的 JS 文件为 about/index.js 不能多不能少即可

观察 src/index.ejs 和 src/about.ejs 以及 src/about/index.ejs 发现,三者内的 head 标记内容基本差不多,唯一不同的是 title 标题内容,结合组件化开发,可以将相同部位代码抽离出来,在 src 下创建 components 文件夹,并创建 html-head.ejs 表示抽离的页面 head 标记内容,由于标题不同,这里引入 EJS 语法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="<%= htmlWebpackPlugin.options.favicon %>" rel="shortcut icon" type="image/x-icon">
    <title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
    <div id="root">
		<!-- 同理抽出底部代码作为 html-foot.ejs 组件内容 --> 
    </div>
</body>
</html>

看 EJS 语法可以得到 title 的值是通过 htmlWebpackPlugin 对象传递的,那么就需要在 htmlWebpackPlugin 对象中声明,在引用组件时,需要注意多级目录问题

# src/index.ejs 修改如下,同理抽离 html-foot.ejs
------------------------------------------------------------------------------------------------------------
<%= require('./components/html-head.ejs')({htmlWebpackPlugin}) %>
	hello webpack
<%= require('./components/html-foot.ejs')({htmlWebpackPlugin}) %>
------------------------------------------------------------------------------------------------------------
# src/about.ejs 修改如下,同理抽离 html-foot.ejs
------------------------------------------------------------------------------------------------------------
<%= require('./components/html-head.ejs')({htmlWebpackPlugin}) %>
	hello webpack
<%= require('./components/html-foot.ejs')({htmlWebpackPlugin}) %>
------------------------------------------------------------------------------------------------------------
# src/about/index.ejs 修改如下,同理抽离 html-foot.ejs
------------------------------------------------------------------------------------------------------------
<%= require('../components/html-head.ejs')({htmlWebpackPlugin}) %>
	hello webpack
<%= require('../components/html-foot.ejs')({htmlWebpackPlugin}) %>

【EJS 语法】

<% --- %> ‘脚本’ 标签,用于流程控制,无输出

<% if (flag) { %>
  <h2>为真的情况</h2>
<% } else { %>
  <h2>为假的情况</h2>
<% } %>

<%= (_var) %> 输出数据到模板(输出是转义 HTML 标签)

# var message = '标题的内容';
<h2><%= message %></h2>

说白了就是 EJS 语法引用的 JS 语法,其他都使用 HTML 语法,了解掌握以上两种 EJS 语法即可

webpack 样式处理

网页结构 html、表现 css、行为 JS 三大要素才能形成一个完整的网页,学过 css 的都知道,有 3 种 样式引入:行内、内部、外联

# 在 src/css 目录下创建样式文件 index.css,内容如下,通过在 src/js/index.js 中引入样式
------------------------------------------------------------------------------------------------------------
#root { color: #f00; }
------------------------------------------------------------------------------------------------------------
# 通过 @import './public.css'; 方式引入其他样式
import '../css/index.css';
------------------------------------------------------------------------------------------------------------
# 安装 css-loader 和 mini-css-extract-plugin,需要配置 rule.config.js 和 plugin.config.js 配置
> npm install css-loader mini-css-extract-plugin --save-dev
------------------------------------------------------------------------------------------------------------
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = [
    ......

    { test: /\.css$/, use: [ MiniCssExtractPlugin.loader, 'css-loader' ] },
];
------------------------------------------------------------------------------------------------------------
......

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

......

plugins.push( new MiniCssExtractPlugin({ filename: 'css/[name].css' }) );

module.exports = plugins;

写页面样式 CSS 一向是一种体力活,非常的枯燥而写死,不能动态改变,需要利用 LESS、SASS 等 CSS 扩展语言,两者并没有什么区别

将 src/css/index.css 修改为 src/css/index.less,并将 src/js/index.js 引入样式 import ‘…/css/index.css’; 改成 import ‘…/css/index.lessss’;

# 安装 less 和 less-loader 预处理器来处理 less 文件转换为 css 文件,引入 less-loader 到 rule.config.js 文件即可
> npm install less less-loader --save-dev
------------------------------------------------------------------------------------------------------------
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// 指定抽出样式内部引用外部资源公共路径,由于打包后 dist/css 样式引用 dist/imgs 图片,publicPath = ’../‘ 相对于父级定位
const extract = { loader: MiniCssExtractPlugin.loader, options: { publicPath: '../' } };
module.exports = [
    ......

    { test: /\.(c|le)ss$/, use: [ extract, 'css-loader', 'less-loader' ] },
];
------------------------------------------------------------------------------------------------------------
# 执行 npm run build 打包,得出的效果没有变化,同样在 src/css/index.less 内部引入 @import './public.less'; 并修改 public.less 样式
# 在 public.less 内部添加 #root { font-size: 16px; } 执行 npm run build 打包,得到的打包样式如下:
------------------------------------------------------------------------------------------------------------
#root { font-size: 16px; }
#root { color: #f00; }
------------------------------------------------------------------------------------------------------------
# 明明同一个样式选择器,这样的写法是不友好的,通过 optimize-css-assets-webpack-plugin 插件去处理重复、压缩样式信息,引用插件到 plugins.config.js
> npm install optimize-css-assets-webpack-plugin --save-dev
------------------------------------------------------------------------------------------------------------
......

const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');

......

plugins.push( new OptimizeCssAssetsPlugin({ assetNameRegExp: /\.css$/g, canPrint: true }) );

module.exports = plugins;
------------------------------------------------------------------------------------------------------------
# 在我们使用 transform 2D 转换样式时,由于各个浏览器的兼容性问题,需要给样式添加前缀来修饰,就需要引入 postcss-loader 预处理器来处理兼容样式问题
# 安装 postcss-loader 预处理器,来处理样式兼容性问题,并配置 rule.config.js 样式兼容设置
> npm install postcss-loader@3.x autoprefixer@9.x --save-dev
------------------------------------------------------------------------------------------------------------
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

const extract = { loader: MiniCssExtractPlugin.loader, options: { publicPath: '../' } };
const plugins = [ require("autoprefixer")({ overrideBrowserslist: ['last 30 versions', "> 2%", "Firefox >= 10", "ie 9-11"] }) ];
const postcss = { loader: "postcss-loader", options: { plugins } };
module.exports = [
	......

    { test: /\.(c|le)ss$/, use: [ extract, 'css-loader', postcss, 'less-loader' ] },
];

webpack 静态资源

网页中同样存在大量的静态资源:图片,在 src/index.html 引入图片静态资源甚至在 CSS 中引入背景图片,一般不直接在 HTML 中使用 img 标记来添加,而是通过异步的方式,这样处理的方式,利于页面加载,具体方法如下:

# 通过一个 DIV 初定一个图片盒子,设置其宽、高,和背景颜色:background-color: #eee;
------------------------------------------------------------------------------------------------------------
<%= require('./components/html-head.ejs')({htmlWebpackPlugin}) %>
    <div class="async-image">

    </div>
<%= require('./components/html-foot.ejs')({htmlWebpackPlugin}) %>
------------------------------------------------------------------------------------------------------------
#root {
    width: 100%; overflow: hidden;
    .async-image { width: 683px; height: 300px; background-color: #eee; }
}
------------------------------------------------------------------------------------------------------------
# 通过在 src/js/index.js 中引入图片,得到图片信息,再基于 DOM 的操作,添加引入图片
------------------------------------------------------------------------------------------------------------
import '../css/index.less';
const imagePath = require('../imgs/webpack.png');			// 这个图片自行网上查找,随便一张图片

const image = document.createElement('img');
image.setAttribute('src', imagePath);
document.getElementsByClassName('async-image')[0].appendChild(image);
------------------------------------------------------------------------------------------------------------
# 安装 file-loader 和 url-loader 处理页面中的图片引用,file-loader 用于输入静态资源,url-loader 用于约束图片,小图片转 base64
> npm install file-loader url-loader --save-dev
# 一定要安装 file-loader,不能超出 url-loader 设定约束大小的图片不会拷贝,设置 rule.config.js 如下
------------------------------------------------------------------------------------------------------------
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

......
const options = { name: '[name].[ext]', limit: 8 * 1024, outputPath: 'imgs', esModule: false };
module.exports = [
    ......

    { test: /\.(png|jpg|jpeg|gif)$/, loader: 'url-loader', options },
    { test: /\.(ttf|eot|woff|woff2|svg)$/, loader: "file-loader", options: { name: '[name].[ext]', outputPath: "fonts" }},
];

webpack 语法转换

随着前端知识的迭代更新,JS 从 es 5~es 6 进步了,甚至 es 7 的地步,JS 越来越完善了,提供了许多 API 方便实际开发应用,当然对于某些不更新的旧版本浏览器的兼容性就没有那么友好了,特别是 IE 浏览器(_~~~ IE 浏览器已经放弃了 Trident 内核更新)

# 在 src/js/index.js 引用 ES 6 的解构方法,进行打包后,分别使用谷歌、IE 浏览器打开 dist/index.html
------------------------------------------------------------------------------------------------------------
let [a, b, c] = [1, 2, 3];
console.log(a, b, c);		// 谷歌:1 2 3	 IE:SCRIPT1010: 缺少标识符
------------------------------------------------------------------------------------------------------------
# 通过安装 babel-loader、@babel/core、@babel/preset-env 对 ES 6 语法转换,引入 JS 预处理器到 rule.config.js
> npm install babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime --save-dev
------------------------------------------------------------------------------------------------------------
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

......
const presetenv = { presets: ["@babel/preset-env"], plugins: ['@babel/plugin-transform-runtime'] };
module.exports = [
    ......
    { test: /\.?js/, loader: 'babel-loader', options: presetenv, exclude: /(node_modules|bower_components)/ },
];
------------------------------------------------------------------------------------------------------------
# 在 src/js/index.js 引用 ES 6 的 Promise 异步请求,进行打包后,谷歌浏览器打印信息,而 IE 浏览器返回错误:SCRIPT5009: “Promise”未定义
------------------------------------------------------------------------------------------------------------
function Log () {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('随便什么数据');
        }, 2000);
    });
}
async function Sum () {
    let a = 1, b = 2;
    let msg = await Log();
    console.log(a + b);		// 需要等 2 秒后同步 msg 执行
    console.log(msg);
}

Sum();
------------------------------------------------------------------------------------------------------------
# 可以通过安装 @babel/polyfill 来全局注入到每个入口文件 entry.config.js 中,但这个会导致打包速度变慢,而且 @babel/polyfill 维护者已经废弃了
> npm install @babel/polyfill --save-dev					# 学习参考作用,学完 npm uninstall @babel/polyfill --save-dev 卸载掉
------------------------------------------------------------------------------------------------------------
const { resolve } = require('path');
const routes = require('./route.config');

let entry = {};
routes.map(item => {
    const index = item.path.indexOf('/') > -1 ? item.path.replace(/\//g, '.') : item.path;
    // 学完还原 entry[index] = resolve(__dirname, './src', `js/${item.path}.js`);
    entry[index] = ['@babel/polyfill', resolve(__dirname, './src', `js/${item.path}.js`)];
});

module.exports = entry;
------------------------------------------------------------------------------------------------------------
# 推荐使用按需引入的方式去,按 @babel/polyfill 维护者提议安装 core-js 依赖配置到 rule.config.js 设置按需加载依赖
> npm install core-js --save
------------------------------------------------------------------------------------------------------------
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

......
const presetenv = { presets: [["@babel/preset-env", { "useBuiltIns": "usage", "corejs": 3 }]], plugins: ['@babel/plugin-transform-runtime'] };
module.exports = [
    ......
];

webpack 库的引用

纵观 src/js/index.js 还是使用的原生方式对 DOM 的进行操作,这种方式虽然能够呈现出效果,但不利于开发效率,在这个前端库泛滥的时代,jquery 依然被淘汰

# 通过安装 vue 2.x 版本,来引入 vue,对比 jquery 库的大小显然 vue 要小的多啦,当然也可以选择其他库,例:react、angular 等
# 通过在 JS 文件中引入 Vue 对象,再进行实例化即可,推荐 const Vue = require('vue/dist/vue'); 方便 Vue 的共享,编辑 src/js/index.js 如下
> npm install vue@2.x --save
------------------------------------------------------------------------------------------------------------
import '../css/index.less';
const Vue = require('vue/dist/vue');
const imagePath = require('../imgs/wKgADlyIZQGAAr6UAAAgYk-FQqs147.jpg');

new Vue ({
    el: '#root',
    data: { imagePath }
});
------------------------------------------------------------------------------------------------------------
# 需要注意的是 vue 对 IE 的底版本不兼容,紧紧维持在 IE 9+ 以上版本

webpack 服务运行

前端工程化除了能够实现组件化开发,每次调整代码都要去 npm run build 就显得非常麻烦,那么,通过 webpack-dev-server

# 通过安装 webpack-dev-server 来启动一个本地服务,方便项目边改边看,提高开发效率,配置 webpack.config.js 服务和设置 package.json 启动指令
> npm install webpack-dev-server --save-dev
------------------------------------------------------------------------------------------------------------
const { resolve } = require('path');
const entry = require('./entry.config');
const plugins = require('./plugin.config');
const rules = require('./rule.config');

module.exports = {
    entry,
    output: {
        filename: 'js/[name].js',
        path: resolve(__dirname, './dist/')
    },
    module: {
        rules: [ ...rules ],
    },
    plugins: [ ...plugins ],
    devServer: {
        contentBase: "dist",            // 设置服务器主文件,默认:index.js index.html
        host: "localhost",              // 设置服务器 IP 地址
        port: 3000,                     // 设置服务器端口号
        progress: true,                 // 开启运行进度输出到控制台
        compress: true,                 // 开启一切服务进行 gzip 压缩
        hot: true,                      // 启用热更新
    },
    mode: 'development'
}
------------------------------------------------------------------------------------------------------------
{
    "name": "webp-demo",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "build": "webpack --progress --colors",
        "dev": "webpack-dev-server --open --hot --watch",
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    ......
}
------------------------------------------------------------------------------------------------------------
# 运行指令 npm run dev 项目自行使用默认浏览器打开 http://localost:3000 本地服务,运行不需要压缩代码,打包需要压缩代码,通过 cross-env 控制
# 通过安装 cross-env 来管理配置运行参数,调整 package.json 文件,以及 webpack.config.js、plugin.config.js
> npm install cross-env --save-dev
------------------------------------------------------------------------------------------------------------
{
    ......
    "scripts": {
        "build": "cross-env MODE=production webpack --progress --colors",
        "dev": "cross-env MODE=development webpack-dev-server --open --hot --watch",
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    ......
}
------------------------------------------------------------------------------------------------------------
const { resolve } = require('path');
const entry = require('./entry.config');
const plugins = require('./plugin.config');
const rules = require('./rule.config');

module.exports = {
    ......
    mode: process.env.MODE              // development 开发模式, production 生产模式
}
------------------------------------------------------------------------------------------------------------
......

let plugins = [];
const minify = (process.env.MODE === 'production');             // minify 压缩和清空注释
routes.map(item => {
    const chunk = item.path.indexOf('/') > -1 ? item.path.replace(/\//g, '.') : item.path;
    plugins.push(
        new HtmlWebpackPlugin({
            ......
            chunks: [chunk], inject: true, path:item.path, minify,
            ......
        })
    );
});

plugins.push( new MiniCssExtractPlugin({ filename: 'css/[name].css' }) );
const optimize = new OptimizeCssAssetsPlugin({ assetNameRegExp: /\.css$/g, canPrint: true });
process.env.MODE === 'production' &&  plugins.push(optimize);

module.exports = plugins;

打包的时候,为了防止 dist 存在缓存信息,在打包之前自动执行一次删除 dist 文件,通过 clean-webpack-plugin 插件执行

# 安装 clean-webpack-plugin 插件用于清除打包根目录 dist,防止打包缓存信息,将清理插件引用到 plugin,config.js 中
> npm install clean-webpack-plugin --save-dev
------------------------------------------------------------------------------------------------------------
const { resolve } = require('path');
const routes = require('./route.config');
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
......

let plugins = process.env.MODE === 'production' ? [new CleanWebpackPlugin()] : [];
......

module.exports = plugins;

webpack 跨域处理

前后端分离开发后,就存在着跨端请求后端接口,请求后端提供的接口就需要处理跨域问题,设置 webpack.config.js 的服务映射的 proxy 设置代理

# 为了方便后期维护,在根目录创建 proxy.config.js 用于处理项目跨域代理问题
------------------------------------------------------------------------------------------------------------
let api = process.env.MODE === 'production' ? "/prod-api" : "/dev-api";
const rewite = process.env.MODE === 'production' ? "^/prod-api/" : "^/dev-api/"

module.exports = {
    api: {
        target: `http://${process.env.SERVE}:${process.env.PORT}`,
        pathRewrite: { rewite: "/" },
        changeOrigin: true
    },
}
------------------------------------------------------------------------------------------------------------
{
    "scripts": {
        "build": "cross-env SERVE=14.215.177.39 PORT=443 MODE=production webpack --progress --colors",
        "dev": "cross-env SERVE=127.0.0.1 PORT=443 MODE=development webpack-dev-server --open --hot --watch",
        "describe": "SERVE:服务器地址,PORT:端口"
    },
}

webpack 项目实战

{
    "name": "webp-demo",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "build": "cross-env SERVE=14.215.177.39 PORT=443 MODE=production webpack --progress --colors",
        "dev": "cross-env SERVE=127.0.0.1 PORT=443 MODE=development webpack-dev-server --open --hot --watch",
        "describe": "SERVE:服务器地址,PORT:端口"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "devDependencies": {
        "@babel/core": "^7.13.10",
        "@babel/plugin-transform-runtime": "^7.13.10",
        "@babel/preset-env": "^7.13.10",
        "autoprefixer": "^9.8.6",
        "babel-loader": "^8.2.2",
        "clean-webpack-plugin": "^3.0.0",
        "cross-env": "^7.0.3",
        "css-loader": "^5.1.2",
        "ejs-loader": "^0.5.0",
        "file-loader": "^6.2.0",
        "html-webpack-plugin": "^4.5.2",
        "less": "^4.1.1",
        "less-loader": "^7.3.0",
        "mini-css-extract-plugin": "^1.3.9",
        "optimize-css-assets-webpack-plugin": "^5.0.4",
        "postcss-loader": "^3.0.0",
        "url-loader": "^4.1.1",
        "webpack": "^4.46.0",
        "webpack-cli": "^3.3.12",
        "webpack-dev-server": "^3.11.2"
    },
    "dependencies": {
        "core-js": "^3.9.1",
        "vue": "^2.6.12"
    }
}
举报

相关推荐

0 条评论