在 webpack 中,每一个文件都是一个模块,除了 webpack 认识的 JS 和 JSON 文件,CSS,图片等都是模块,模块之间经过不一样方式的语法会产生依赖关系,例如:css
webpack 经过loader来支持多种语言和预处理器语法编写的模块,这样就能够处理各类非 JS 内容。html
以前的配置是直接在 webpack 的打包输出目录下建立一个 html 页面,并手动引入 webpack 输出的 JS 文件。尽管引入了 WDS,能够自动打开并刷新页面,可是这只是一个 bundle 文件的状况,若是项目模块增多,须要多个 bundle 时,天然没法再手动将 bundle 文件添加到 html 中,因此咱们须要一个插件可以将 bundle 文件自动添加到 html 中。react
HtmlWebpackPlugin 是在 webpack 打包过程当中自动生成 html 页面,而且自动将资源文件,例如 webpack 打包输出的 JS 文件插入到 html 页面中的插件。根据配置,这个插件还能支持根据自定义的模板来生成 html 文件,或者根据ejs的语法规则来替换一些变量。webpack
yarn add html-webpack-plugin -D复制代码
配置项 | 类型 | 默认值 | 用法 |
---|---|---|---|
title | String | Webpack App | html 的 title |
filename | String | 'index.html' | 生产的 html 页面的文件名 |
template | String | src/index.ejs | 模板页面,若是存在src/index.ejs就是用它 |
templateContent | String|Function|false | false | 代替模板使用以提供嵌入式模板 |
templateParameters | Boolean|Object|Function | false | 覆盖模板中使用的参数 |
inject | `true | false <br />'head' | 'body'` |
scriptLoading | 'blocking' 'defer' |
'blocking' | 设置 JS 加载的方式 |
favicon | String | `` | 将给定的 favicon 路径添加到 html 中 |
meta | Object | { } | 为 html 模板页面添加 meta,参见 —— HEAD#meta |
base | Object|String | false | false | 为 html 模板页面添加 base 标签 |
minify | Boolean | Object | true | 根据 webpack 的mode配置来判断当前环境,因为mode默认是production生产环境,因此这个值默认也是true,也就是对 html 进行压缩;也可使用一个对象来配置具体的压缩项目,参见 —— minification |
hash | Boolean | false | 若是为true,则将惟一的 webpack 编译哈希值附加到全部包含的脚本和 CSS 文件中。这对于清除缓存颇有用 |
cache | Boolean | true | 仅在文件改变时从新生成 |
showErrors | Boolean | true | 错误详细信息将写入 HTML 页面 |
chunks | Array of String | ? | 自定义添加的模块 |
chunksSortMode | "none" "auto" "manual" Function |
"auto" | 容许控制在将块包含到 HTML 中以前应如何对其进行排序 |
excludeChunks | Array of String | 打包时忽略的模块,例如测试模块等 | |
xhtml | Boolean | false | 若是设置成true,link标签的插入形式将是自动闭合的形式,也就是闭合的箭头括号前多一个斜杠 |
若是不使用自定义的模板,HtmlWebpackPlugin 配置最简单,直接new HtmlWebpackPlugin()就能够了。github
const HtmlWebpackPlugin = require("html-webpack-plugin");module.exports = { ... plugins: [new HtmlWebpackPlugin()], }复制代码
可是实际项目每每基本都须要自定义模板,HtmlWebpackPlugin 能够根据自定义的模板来生成 html 文件,并将 webpack 的输出资源插入到 html 页面中。web
HtmlWebpackPlugin 自己内置识别.ejs文件的 loader,若是你的模板使用.ejs来定义,那么就无需安装其余 loader 了。固然了,HtmlWebpackPlugin 也提供了许多其它类型的模板能够选择,参见 —— template option。正则表达式
接下来新建一个public文件夹,将以前 dist 目录的index.html移入其中,而后把<body>里以前引入的main.js干掉,将这个index.html做为模板页面配置在 HtmlWebpackPlugin 中,而后再把 dist 目录删掉。跨域
const HtmlWebpackPlugin = require("html-webpack-plugin");module.exports = { ... plugins: [new HtmlWebpackPlugin({template:"./public/index.html" })], }复制代码
接下来,须要将 webpack-dev-server 配置里的contentBase删除掉,已经再也不须要提供静态文件了,如今依据html-loader和html-webpack-plugin能自动生成一个 html 页面做为 webpack-dev-server 的调试页面了,即便执行生产环境的yarn build命令也能依据靠它们将 html,icon 等静态文件输出到打包目录dist中。
执行yarn start命令,就能够看到自动在浏览器中打开了页面,经过 devtool 也能看到 bundle 文件已经自动插入到指定的 html 中了。
能够为 html 页面设置一个 favicon,HtmlWebpackPlugin 会根据favicon配置的路径找到文件而后添加到 html 中。
plugins: [ new HtmlWebpackPlugin({template: "./public/index.html",favicon: "./public/favicon.ico", }), ],复制代码
在 React 开发过程当中若是要使用 CSS,最多见的作法是在当前组件中经过import引入 CSS 文件,可是 CSS 最终须要被放在 HTML 页面中才能被加载,解析。
要将 CSS 自动插入到 html 页面中,须要使用style-loader,光引入style-loader还不行,得须要一个根据import语法解析 CSS 的 css-loader,css-loader负责解析 CSS 文件中的样式生成字符串,而后style-loader默认建立<style>标签塞入 CSS 字符串,最后插入到页面中。
yarn add style-loader css-loader -D复制代码
//简单配置module.exports = { ... module:{rules:[ ... {test: /\.css$/i, use: [ {loader: "style-loader", }, {loader: "css-loader", }, ], }, ] } }复制代码
默认是以<style>的形式将组件中引入的 CSS 插入到 DOM 中
配置项 | default | 含义 |
---|---|---|
injectType | styleTag | 把样式插入到 html 的方式,默认是经过<style>标签,能够选择linkTag,也就是<link>标签 |
attributes | {} | 写入<style>或者<link>标签的属性 |
insert | head | 插入<style>或者<link>标签的位置,默认就是在 html 的<head>中 |
base | base 容许你经过指定一个比 DllPlugin1 使用的 css 模块 id 大的值,来避免应用程序中的 css (或者 DllPlugin2 的 css) 被 DllPlugin1 中的 css 覆盖问题 | |
esModule | false | 默认状况下,style-loader 生成使用 Common JS 模块语法的 JS 模块;若是指定使用 ES module,有利于 tree shaking |
配置项 | 默认值 | 含义 |
---|---|---|
url | true | 默认启用对url/image-set动态加载资源的处理 |
import | true | 默认支持@import规则 |
modules | false | 默认是基于文件名选择是否支持 CSS Modules |
esModule | true | css-loader 生成默认使用 ES 模块语法 |
importLoaders | 0 | 设置在 CSS 加载程序以前应用的 loader 的数量 |
sourceMap | false | 取决于 webpack 的devtool设置项,只在devtool的值不是eval和false的时候这个配置项才有用 |
须要特别关注的是modules这个配置,它和CSS Modules相关。由于单页面应用开发中使用 CSS 一个最大的问题就是很差维护,特别容易出现命名混乱,样式覆盖等问题,使用 CSS Modules 能够有效避免这些问题。
css-loader默认是支持 CSS Modules 的,它是根据在 React 中import引入的 CSS 文件名来判断这个 CSS 文件是否是一个模块,若是是就去使用内置的解析规则去替换一些 class 名称等。正则匹配文件名规则是/\.module\.\w+$/i,也就是忽略大小写,文件名中包含.module就被看成一个 CSS 模块。
Note:若是你eject过 CRA 的代码就会发现,CRA 就是采用css-loader这种默认配置的方式。
所以若是你将一个 CSS 文件名命名为xxx.module.css这种形式之后,在 React 组件中使用import引入这个 CSS 文件的时候,必须使用 CSS Modules 指定的规则,也就是把导入的 CSS 看做一个对象,在className中去使用,以下:
/* styles.module.css */.test { color: red; }复制代码
import React, { Component } from 'react';import styles from './styles.module.css';export default class extends Component { render() {return ( <div><p className={styles.test}>12121212</p> </div>); } }复制代码
除了使用默认配置, modules可供选择的设置还有这些:
属性值 | 含义 |
---|---|
true | 所有使用 CSS Modules,设置成这个之后,在 React 中引入 CSS 就必须按照上面那种 CSS Modules 的写法 |
false | 禁用 CSS Module |
"local" | 和true是同样的,CSS Modules 中的class只具备模块做用域 |
"global" | 将 CSS Modules 设置成全局做用域 |
Object Type | modules能够设置成一个对象,属性以下表所示,见 —— Object |
以modulesObject 形式配置localIdentName 来看,localIdentName 能够指定替换 React 中className的名称,localIdentName 采用loader-utils#interpolatename中的模板字符串替换形式。推荐是开发环境使用"[path][name]__[local]",生产环境使用"[hash:base64]",配置一下试试。
modules: { localIdentName: isDevelopment ? "[path][name]__[local]" : "[hash:base64]", },复制代码
开发环境的生成结果:
yarn build打包的结果:
PostCSS是一个开源的用 JS 编写的 CSS 解析器,它可以将 CSS 文件解析成抽象语法树,从而使用 PostCSS 的插件能够基于抽象语法树来转换 CSS 的语法,例如添加浏览器兼容前缀-webkit-(webkit 引擎),-moz-(firefox 的排版引擎)和-ms-(IE 的排版引擎),或者将代码开发阶段使用的未被浏览器普遍支持的 CSS 语法转换成兼容性更强的语法等。例如 CSS 的样式规范工具stylelint就是基于 PostCSS 的。
postcss-loader是让 PostCSS 能够在 webpack 构建工做流中去解析 CSS 生成 AST 的 loader,通常来讲是这样的,在 webpack 基于模块间的依赖关系编译完了 JS,CSS,或者 Less 等之后,这些 CSS 文件会被送到 PostCSS 去解析成 AST,而后基于 PostCSS 的插件就能够针对 AST 去干一些有意义的事情,好比说兼容性修改等。
yarn add postcss-loader postcss -D复制代码
postcss-loader的配置项是相对简单的,只有三个:
module.exports = { module: {rules: [ {test: /\.css$/i, use: [ 'style-loader', 'css-loader', {loader: 'postcss-loader',options: { postcssOptions: {//options }, }, }, ], }, ], }, };复制代码
单纯使用postcss-loader没什么卵用,最主要的是引入基于PostCSS 的插件,经常使用的例如:
yarn add autoprefixer postcss-preset-env postcss-flexbugs-fixes -D复制代码
修改上文配置,引入插件
module.exports = { module: {rules: [ {test: /\.css$/i, use: [ 'style-loader', 'css-loader', {loader: 'postcss-loader',options: { postcssOptions: {plugins: [ 'postcss-flexbugs-fixes', 'autoprefixer', 'postcss-preset-env', ], }, }, }, ], }, ], }, };复制代码
如今用::placeholder这个伪元素来测试一下,不管是开发环境仍是生产环境都自动添加了厂商前缀,可是autoprefixer在兼容grid布局方面有一些不足,见 —— Does Autoprefixer polyfill Grid Layout for IE,它只能转换到 IE 10 和 IE 11 的程度。
生产环境中须要将 React 中import引入的 CSS,或者 less 等导出为单个 CSS 文件,经过<link>标签插入到 DOM 中,推荐使用mini-css-extract-plugin这个 webpack plugin,它能够为每一个包含 CSS 的 JS 文件建立一个 CSS 文件,它支持 CSS 和 SourceMap 的按需加载。相比extract-text-webpack-plugin来讲,mini-css-extract-plugin特色以下:
yarn add mini-css-extract-plugin -D复制代码
修改上文的style-loader配置,同时配置mini-css-extract-plugin内置的 loader 和 plugin
const MiniCssExtractPlugin = require("mini-css-extract-plugin");module.exports = function (env) { const isDevelopment = env.NODE_ENV === "development"; const isProduction = env.NODE_ENV === "production"; return { ...module: { rules: [ ... { test: /\.css$/i, use: [ isDevelopment && { //开发环境使用style-loader loader: "style-loader", }, isProduction && { //生产环境使用mini-css-extract-plugin loader: MiniCssExtractPlugin.loader, options: {publicPath: "../../",//由于提取的CSS文件最终位于 static/css文件夹中,因此往上两层 }, }, { loader: "css-loader", options: {modules: { localIdentName: isDevelopment ? "[path][name]__[local]": "[hash:base64]", }, }, }, ].filter(Boolean), }, ], },plugins: [ ... isProduction && //还须要配置启用plugin部分new MiniCssExtractPlugin({ filename: "static/css/[name].[contenthash:8].css", }), , ].filter(Boolean), }; };复制代码
执行yarn build看一下输出的文件,已经在dist目录下根据配置的路径生成了 css 文件
再检查 html 页面,发现 CSS 文件按照路径经过<link>的形式插入
移除无用的 CSS 可使用purgecss,purgecss的文档介绍了不少 plugin 和框架结合的用法,其中我重点关注的是结合 CSS modules 如何使用purgecss —— How to use with CSS modules,这部份内容在purgecss的介绍网站贴出来了,GitHub 的 README 页面并未说起,在 issue 中有一个相关问题 —— Webpack 4 + React + CSS modules stripping all classes from CSS bundle。
根据 issue 中相关讨论,找到了解决方案,见 —— github.com/FullHuman/p…
yarn add @fullhuman/postcss-purgecss glob -D复制代码
content指定一个文件路径字符串数组,在@fullhuman/postcss-purgecss里是指定代码中经过import引入过 CSS 的地方,指定这些文件路径之后就会去检查它们引入的 CSS 文件中是否包含未使用的 CSS 代码;
glob是一个负责根据通配符匹配文件路径的工具,这里使用glob.sync(pattern, [options])这个方法,去查找全部src目录以及子目录中.jsx结尾的 React 文件。
module.exports = { module: {rules: [ {test: /\.css$/i, use: [ 'style-loader', 'css-loader', {loader: 'postcss-loader',options: { postcssOptions: {plugins: [ 'postcss-flexbugs-fixes', 'autoprefixer', 'postcss-preset-env', ['@fullhuman/postcss-purgecss', //配置@fullhuman/postcss-purgecss{ content: [ path.join(__dirname, './public/index.html'), ...glob.sync( `${path.join(__dirname, 'src')}/**/*.jsx`, {nodir: true, }, ), ], }, ], ], }, }, }, ], }, ], }, };复制代码
通过上述配置,现定义一个 button 组件和 CSS 文件,可是只经过 CSS Modules 使用其中一个 CSS 类,执行构建,最终生成的 CSS chunk 确实移除了未使用过的.btn1的代码。
.btn1 { background: red; } .btn2 { background: green; }import React, { Component } from "react";import styles from "./styles.css";export class Button1 extends Component { render() {return <button className={styles.btn2}>测试1</button>; } }复制代码
mini-css-extract-plugin只负责生成 CSS 文件,要对生产环境打包后的 CSS 文件进行压缩,须要使用额外的插件 —— optimize-css-assets-webpack-plugin。
yarn add optimize-css-assets-webpack-plugin -D复制代码
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); //压缩CSS代码module.exports = { plugins: [new OptimizeCssAssetsPlugin({ assetNameRegExp: /\.css$/g, cssProcessor: require('cssnano'), cssProcessorPluginOptions: {preset: ['default', { discardComments: { removeAll: true } }], }, canPrint: true, }), ], };复制代码
若是在 CSS 中使用url引入一个图片,或者在 React 中直接import一个图片,都须要额外的 loader 来解析图片,webpack 文档中给出了两种 loader 来处理图片资源:file-loader和url-loader。
yarn add file-loader url-loader -D复制代码
file-loader支持解析 React 中import的图片路径以及在 CSS 中使用url引入的图片,默认状况下,file-loader会对引入的图片从新生成一个 hash 字符串做为替换名称。
module.exports = { ... modules:{rules:[ ... {test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/, /\.svg$/], //注意不要忘了svgloader: "file-loader", } ] } }复制代码
若是不对file-loader配置打包后的图片输出目录,在开发环境一般没有影响,可是在执行yarn build之后,图片都会被放在 webpack 的output指定的根目录下面。因此必须为file-loader指定打包后的输出目录。
能够经过name和outputPath指定图片输出目录
module.exports = { ... modules:{rules:[ ... {test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],loader: "file-loader",options: { name: "[name].[hash:8].[ext]", outputPath: "static/images", }, } ] } }复制代码
这样的配置结果,最终打包输出图片的目录会和 html 在同级目录的static/images下
├─ dist ├─ favicon.ico ├─ index.html └─ static ├─ css │ ├─ main.css └─ images └─ picture.jpg复制代码
可是这样的打包目录对于在 CSS 中经过url引入的图片来讲,它也会被放到static/images目录下,同时路径前缀也是static/images,因此 CSS 就找不到图片了。这种状况我目前找到两种解决方式:
第一种:在以前提取 CSS 的插件mini-css-extract-plugin的 loader 配置中直接指定 CSS 的公共资源目录,这样在打包完了之后,CSS 中引用的资源路径会在static/images前面再加上../../的前缀,这样路径就对了
isProduction && { //生产环境使用mini-css-extract-plugin loader: MiniCssExtractPlugin.loader, options: {publicPath: '../../', }, },复制代码
第二种:经过file-loader的name的函数配置方式解决,name的函数会接收三个参数:
这种方式我我的想法是对于在 CSS 中引用的图片,设置固定格式的文件名,例如cssimage-xxx.jpg,而后name函数在接收到文件名根据正则表达式去匹配,若是知足是 CSS 中引用的图片文件名格式,那么返回的文件名路径就可使用特定的形式来指定,例如../../static/images/[name].[hash:8].[ext],若是不是就使用static/images/[name].[hash:8].[ext]。
url-loader是file-loader的升级,对于在limit限制内的小图片,url-loader将图片转成Base64 编码的数据,并经过
Data URLs放在页面中,或者 CSS 中,而对于超过了limit限制的图片,能够指定后续经过file-loader处理,也能够直接为其指定输出路径。
以data://形式开头的 Data URLs 协议是众多 URI 协议中的一种,URI 自己就是统一资源标识符的缩写,因此 Data URLs 也是惟一标识一个资源的形式。
Data URLs 的形式以下:
data:[<mediatype>][;base64],<data>
mediatype,也就是图片的文件类型
;base64这部分字符串时可选的,也就是说你能够直接将某些 mediatype 的文本嵌入到 Data URLs 中,例如 HTML,SVG 等
data,多是被 Base64 编码之后的数据,也多是纯文本
经过 Data URLs 协议引入的图片一般以下所示:
至于 Base64,是使用 ASCII 码中的 64 个可打印字符(a~z,A~Z,0~9以及+和/,最后还有一个=后缀)来编码数据,这种编码的特色是将原数据的每 6 个bit用一个打印字符来表示,也就是一个字符只能表示3/4的数据量,因此通过 Base64 编码的数据,最终会比原始数据大1/3左右。
将 Base64 编码应用在图片上的话,好处有如下这些:
不用在开发时候管那些麻烦的图片路径配置问题;
对于小图片,直接嵌入 HTML 页面或者 CSS 中,节省 HTTP 请求,至关于缩短页面资源在请求过程当中的排队时间;
图片直接使用 Base64 编码之后,在 JS 中获取图片能够避免一些跨域使用图片的问题
固然,也有一些局限性:
若是把大图片都转成 Base64 塞到 HTML 或者 CSS 里,会致使页面渲染速度明显减慢,并且还会卡;
浏览器没法针对 Base64 编码的图片单独缓存,要么缓存整个 CSS 或者 HTML 文件;
因为 Base64 上面说的比原图片体积大的问题,须要针对服务器开启gzip压缩传输,这样和原图开启gzip传输基本差很少了
因此通常是推荐一些页面的小图标,小 logo 这些不容易改变,且图片体积小的状况下使用 Base64 编码。
url-loader默认是无限制的把全部图片都经 Base64 编码转换,因此必须配置大小限制limit,limit的单位是字节,推荐是10KB如下的图片进行 Base64 编码;对于大于10KB的图片,使用指定文件名的形式来配置。
module.exports = { ... modules:{rules:[ ... {test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],loader: "url-loader",options: { limit: 10 * 1024, name: "static/images/[name].[hash:8].[ext]", }, } ] } }复制代码
SVG 通常在网页中能够经过三种方式使用:
开发的时候,通常不会选择第一种方式,很差看也不利于维护。因此通常是作成模块,经过<img>或者url形式去引入。因此在上面的配置中,还须要添加一种 SVG 的文件格式匹配/\.svg$/i。
单个的 SVG 文件通常都比较小,若是按照上面的url-loader配置,SVG 通常会被转成 Base64 而后经过 Data URLs 放在<img>或者url里面,可是 SVG 自己是能够直接放在 Data URLs 里的,若是转成 Base64 之后再放进去,数据量变大了1/3,徒增 HTML 或者 CSS 文件的体积,这是反向优化!因此不可取,对 SVG 须要额外的处理。
url-loader是推荐了一个mini-svg-data-uri来处理 SVG,这个工具能够移除原 SVG 文件中注释,空格等乱七八糟没用的字符,同时会对 SVG 中的字符进行 URL 编码,避免一些旧的 IE 上不兼容的问题,而后再经过url-loader被放在 Data URLs 里。
yarn add mini-svg-data-uri -D复制代码
const svgToMiniDataURI = require('mini-svg-data-uri');module.exports = { module: {rules: [ ...{test: /\.svg$/i, use: [ {loader: 'url-loader',options: { generator: content => svgToMiniDataURI(content.toString()), }, }, ], }, ], }, };复制代码
import logo from 'logo.svg';export default class extends Component { render() {return <img src={logo} />; } }复制代码
若是想直接在页面里使用这个 SVG 图片,而不是放在<img>标签里,可使用 airbnb 出品的babel-plugin-inline-react-svg,这个插件我在测试的时候,会和上面的url-loader有必定的冲突。它提供的功能是:
yarn add babel-plugin-inline-react-svg -D复制代码
module.exports = { module: {rules: [ {test: /\.m?jsx?$/, exclude: /(node_modules)/, use: { loader: "babel-loader", options: {presets: ["@babel/preset-env", "@babel/preset-react"],plugins: [ "@babel/plugin-proposal-class-properties", "inline-react-svg", //注意到这是一个babel的plugin引入 ].filter(Boolean), }, }, ... } }复制代码
测试一下,SVG 能够经过组件的方式被插入到页面中了。
import Logo from 'logo.svg';export default class extends Component { render() {return <Logo />; } }复制代码
而当我在项目配置resolve.alias的时候,想用 alias 去引入一个 SVG 组件时,babel-plugin-inline-react-svg不支持 alias 解析,一直报错。
import View from '@/assets/icons/view.svg';复制代码
我在babel-plugin-inline-react-svg的 issue 也提了这个问题,插件做者回复是由于 babel 在 webpack 应用 alias 以前已经开始运行了。后来找到了两个解决方法:
一是安装babel-plugin-module-resolver,这个是 babel 的 plugin,帮助 babel 解析 alias 路径问题,配合babel-plugin-inline-react-svg使用。
yarn add babel-plugin-module-resolver -D复制代码
module.exports = { module: {rules: [ {test: /\.m?jsx?$/, exclude: /(node_modules)/, use: { loader: "babel-loader", options: {presets: ["@babel/preset-env", "@babel/preset-react"],plugins: [ "@babel/plugin-proposal-class-properties", "inline-react-svg",// apply babel-plugin-inline-react-svg ["module-resolver", // apply babel-plugin-module-resolver{ alias: {"@": "./src", }, }, ], ], }, }, ... }复制代码
另外一个解决方式是换成@svgr/webpack 这个 webpack loader 来解析 SVG 组件。
yarn add @svgr/webpack -D复制代码
module.exports = { module: {rules: [ {test: /\.svg$/, use: [ {loader: '@svgr/webpack',options: { native: true, }, }, ], }, ], }, };复制代码
上面的 SVG 方案都是一次引入一个 SVG 图像,这种方式在页面图标多的时候很是麻烦,20 多个图标都得 20 个import。
SVG sprite 是利用和之前 CSS sprite 相同的方案思路,将多个 SVG 图像合并到一个 SVG 文件中,最终在页面中只显示一个特定 SVG 图像。SVG sprite 只适合多个小的 SVG 图标整合,对于大的 SVG 图像看成普通图片用上面的方法处理比较合适。
通过必定的 webpack 配置,在 React 中能够直接使用 SVG sprite,方法是先将全部 SVG 图像在项目外面合成一个 SVG spritesprite.svg,内容以下:
<svg width="0" height="0" class="hidden"> <symbol viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" id="view"><path d="M512 608a96 96 0 1 1 0-192 96 96 0 0 1 0 192m0-256c-88.224 0-160 71.776-160 160s71.776 160 160 160 160-71.776 160-160-71.776-160-160-160"></path><path d="M512 800c-212.064 0-384-256-384-288s171.936-288 384-288 384 256 384 288-171.936 288-384 288m0-640C265.248 160 64 443.008 64 512c0 68.992 201.248 352 448 352s448-283.008 448-352c0-68.992-201.248-352-448-352"></path> </symbol> <symbolviewBox="0 0 1024 1024"xmlns="http://www.w3.org/2000/svg"id="view_off" ><path d="M512 800c-66.112 0-128.32-24.896-182.656-60.096l94.976-94.976A156.256 156.256 0 0 0 512 672c88.224 0 160-71.776 160-160a156.256 156.256 0 0 0-27.072-87.68l101.536-101.536C837.28 398.624 896 493.344 896 512c0 32-171.936 288-384 288m96-288a96 96 0 0 1-96 96c-14.784 0-28.64-3.616-41.088-9.664l127.424-127.424C604.384 483.36 608 497.216 608 512m-480 0c0-32 171.936-288 384-288 66.112 0 128.32 24.896 182.656 60.096l-417.12 417.12C186.72 625.376 128 530.656 128 512m664.064-234.816l91.328-91.328-45.248-45.248-97.632 97.632C673.472 192.704 595.456 160 512 160 265.248 160 64 443.008 64 512c0 39.392 65.728 148.416 167.936 234.816l-91.328 91.328 45.248 45.248 97.632-97.632C350.528 831.296 428.544 864 512 864c246.752 0 448-283.008 448-352 0-39.392-65.728-148.416-167.936-234.816"></path><path d="M512 352c-88.224 0-160 71.776-160 160 0 15.328 2.848 29.856 6.88 43.872l58.592-58.592a95.616 95.616 0 0 1 79.808-79.808l58.592-58.592A157.76 157.76 0 0 0 512 352"></path> </symbol></svg>复制代码
而后很关键的,这是一个静态文件,个人作法是把它直接和 HTML 页面放在一个文件夹下,也就是都放在直接建的public里面。在开发环境下,须要在 WDS 里面经过contentBase配置静态文件的目录,表示服务器在请求的时候要带上这些静态文件,以下:
module.exports = { ... devServer:{ ...contentBase: "public", } }复制代码
这时候就能够直接在 React 里面经过<use>使用上面sprite.svg里的任意一个图标了,还能经过fill,height或者width等属性修改图标。注意两点:
export default class extends Component { render() {return ( <div><svg> <use href="sprite.svg#view" fill="red" /></svg><svg> <use href="sprite.svg#view_off" /></svg> </div>); } }复制代码
若是要执行打包yarn build,须要借助一个 webpack 的插件 —— copy-webpack-plugin,这个插件能够将指定目录的文件复制到另外一个目录中,而且复制过程还能够借助一些工具来压缩文件,例如可使用SVGO的工具来优化 SVG 文件
yarn add copy-webpack-plugin -D复制代码
const CopyPlugin = require('copy-webpack-plugin');module.exports = { plugins: [new CopyPlugin({ patterns: [{ from: 'public/sprite.svg' }], //将sprite.svg复制到打包输出目录}), ], };复制代码
这种方式十分简单,而且能节省 SVG 文件的请求;可是不利于维护,图标更新须要手动修改文件,必须保证每一个 SVG 图标的 id 是惟一的,图标愈来愈多的状况下,很乱,同时没法按需加载,页面只要包含一个图标文件,整个sprite.svg都会被请求加载。
解决按需加载和方便维护的一个方法是使用 JetBrains 出品的svg-sprite-loader,它能够在运行的时候根据组件中引入的 SVG 文件,动态地将这些文件合并成sprite.svg,而后插入到 HTML 中,测试一下:
首先,在项目中新建一个专门用来放图标的文件夹src/assets/icons,普通的 SVG 图像就不要放进去了,专门用来管理图标的。
把刚才那两个合成sprite.svg的图标分开放进去,因而有了下面的目录结构:
src ├─ assets │ ├─ icons │ │ ├─ view.svg │ │ └─ view_off.svg │ ├─ images │ │ ├─ picture.svg复制代码
在 webpack 中引入svg-sprite-loader并配置,须要注意的是使用 webpack 的include和exclude对不一样用处的 SVG 进行区分:
yarn add svg-sprite-loader -D复制代码
module.exports = { module: {rules: [ ...{oneOf: [ {test: /\.svg$/i, exclude: path.resolve(__dirname, 'src/assets/icons'), //忽略icon文件夹use: [ {loader: 'url-loader', }, ], }, {test: /\.svg$/i, include: path.resolve(__dirname, 'src/assets/icons'), //只处理icon文件夹use: 'svg-sprite-loader', }, ], }, ], }, };复制代码
这样在组件中importSVG 文件之后,能直接经过<use>使用图标:
import '../../assets/icons/view.svg';export default class extends Component { render() {return ( <div><svg> <use href="#view" /></svg> </div>); } }复制代码
和 CRA 集成 —— github.com/JetBrains/s…
image-webpack-loader是使用imagemin进行图片压缩的 loader,能够在url-loader或者file-loader的基础上进行图片压缩。
yarn add image-webpack-loader -D复制代码
// 在url-loader的基础上使用module.exports = function(env) { const isDevelopment = env.NODE_ENV === 'development'; return {module: { rules: [ { test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/, /\.svg$/i], include: path.resolve(__dirname, 'src/assets/images'), use: [ { loader: 'url-loader', options: {limit: 10 * 1024, //10KBname: 'static/images/[name].[hash:8].[ext]', }, }, { loader: 'image-webpack-loader', //引入image-webpack-loader options: {disable: isDevelopment, // webpack@2.x and newer }, }, ], }, ], }, }; };复制代码
默认配置状况下,对图片的压缩能够减小约60%的图片体积,能够看到压缩前图片7.39MB,压缩后仅为2.32MB
须要注意的是,自己压缩图片是一件缓慢的事,我在上面一张图片的压缩大概耗费了 6s 的时间呢,因此指定disable: isDevelopment在开发环境下会禁用image-webpack-loader。
通常状况下,字体文件能够用做文本的渲染,或者用来处理 IconFont,即经过@font-face定义 IconFont,而后在 CSS 使用@font-face定义的图标。
file-loader和url-loader都能用来处理字体文件,若是经过 CSS 的@font-face中的src来引入 web 字体,例如加载服务器上的字体文件,那么须要 loader 来处理。
以配置使用url-loader为例:
module.exports = { ... module: {rules:[ ... {test: [/\.ttf/i, /\.woff/i, /\.woff2/i, /\.eot/i, /\.otf/i],loader: "url-loader",include: path.resolve(__dirname, "src/assets/fonts"),options: { limit: 10 * 1024, //10KB name: "static/fonts/[name].[hash:8].[ext]", }, }, ] } }复制代码
这样就可使用@font-face的url加载服务器上的字体文件了
@font-face { font-family: 'heiti2'; src: url('../../assets/fonts/heiti2.ttf'); }body { font-family: 'heiti2'; }复制代码
通常中文字体包包含大量的中文字符,致使中文字体包通常都很是的大,随便一个均可能都是几MB,若是网页全局使用字体包中的字体,会影响网页的呈现速度。
在引入字体以前对字体包进行压缩是一个解决方案,这里使用开源工具字蛛,使用 font-spider,首先新建一个空文件夹,内部新建一个 html 文件,而且将原字体包放在里面,以下:
<!DOCTYPE html><html lang="zh-hans"> <head><meta charset="utf-8" /><style> @font-face {font-family: 'heiti2';src: url('./heiti2.ttf'); } body {font-family: 'heiti2'; }</style> </head></html>复制代码
而后安装font-spider
yarn global add font-spider复制代码
进入刚才新建的 html 文件的目录,开始执行压缩命令
font-spider ./index.html复制代码
执行完之后,目录下面会生成一个备份目录.font-spider用来保存原字体文件,而原来的字体文件已经被替换成了压缩之后的字体文件,这个压缩效果,目录结构以下:
minify ├─ .font-spider │ └─ heiti2.ttf ├─ heiti2.ttf ├─ index.html复制代码
这个压缩效果仍是十分强悍的,通过测试,原字体文件2.17MB,压缩之后只有6KB了。不过须要注意的是,这里的压缩只是一个测试,在 webpack 中使用的话,目前 font-spider 没有相应的方案,这个项目已经好久没维护了,见讨论 —— 如何在 webpack 中使用本工具 #150。若是想 webpack 配套使用,须要在打包之后,额外执行一下font-spider压缩命令,而后可能还须要检查一下字体引用路径是否正确。