学习即进步。本人一直使用vue框架,对于react框架一直是得闻不如一见。在一些简单的网站学习了react框架的基本教程之后,参照本人一直使用的webpack+bable+vue+iview开发环境。对应着想搭建一个基于react的开发环境,借此来对比,看看到底是哪个框架比较好用,效率高。进过分析,比对,我选择了与react最为般配的ant-design作为前端ul框架。即模式确定为webpack+bable+react+ant-design;如果需要偷懒,可以直接下载我的github项目,开包即用。
先挂一下我的package.json,小白可以先忽略。项目结束之后可以提供参考。注意 我使用的是webpack4.x的版本。与3.x的部分内容可能不兼容。
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-plugin-import": "^1.11.0",
"babel-preset-env": "^1.6.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"css-loader": "^2.1.0",
"file-loader": "^3.0.1",
"html-webpack-plugin": "^3.2.0",
"less": "^2.7.3",
"less-loader": "^4.0.5",
"mini-css-extract-plugin": "^0.5.0",
"optimize-css-assets-webpack-plugin": "^5.0.1",
"style-loader": "^0.23.1",
"url-loader": "^1.1.2",
"webpack": "^4.29.5",
"webpack-cli": "^3.2.3",
"webpack-dev-server": "^3.1.0",
"webpack-merge": "^4.1.0",
"uglifyjs-webpack-plugin": "^2.1.1"
},
"dependencies": {
"react": "^16.0.0",
"react-dom": "^16.0.0",
"antd": "^3.13.5"
}
根目录
npm init
指令进行npm的初始化。按照提示依次键入信息,不知道的直接回车。根目录>cnpm i webpack --save-dev
npm install -g cnpm --registry=https://registry.npm.taobao.org
"devDependencies": {
"webpack": "^4.0.0",
}
说明1: webpack版本只要是4之后都可以。我这里只是演示
说明2: devDependencies是开发依赖,只会在打包过程中用到,不会包含到最后的代码中
说明3: 如果想安装指定版本的webpack,使用npm install --save-dev webpack@<版本号>格式
我们的项目需要脚本,html,样式,图片以及一些其他资源。
│ package.json
├───node_modules
│ └╌╌ 下面是npm包
├───build
│ ├╌╌╌╌╌ build.js
│ ├╌╌╌╌╌ webpack.base.conf.js
│ ├╌╌╌╌╌ webpack.dev.conf.js
│ └╌╌╌╌╌ webpack.prod.conf.js
├───src
│ ├╌╌╌╌╌ main.js
│ └╌╌╌╌╌template
│ └╌╌╌╌╌template.html // 首页的模板
│ └╌╌╌╌╌images
│ └╌╌╌╌╌logo.jpg
对,暂时我们就准备这些。
webpack的工作就一个。根据一个入口文件,通过import,require等建立目标文件之间的关联关系,调用对应文件后缀的loader实现按需加载。有关完整的配置,可以参考我的下一篇文章。我们今天讲的,都是基本的配置。
首先我们配置文件,打开webpack.base.conf.js
,填写如下内容。
module.exports = {
entry: './src/main', //main.js中的.js可以省略,前面的./不能省
}
有些朋友也许会问,build目录下有那么多js。为什么是webpack.base.conf.js。
首先,我们知道开发一个项目。有开发阶段和打包发布阶段,所以webpack的工作配置,也分为dev
(开发)阶段配置和prod
(发布产品)配置。开发模式与产品模式的webpack配置最主要的区别就是开发时需要一个热更新的服务器。以便于我们写的代码能够保存一次就能看到最新效果。而发布之后就是生成静态html文件,由产品服务器托管了。还有很多其他的区别,在之后我们会一一讲到。但是,大部分的基本配置都是一样的,所以我们分了3个文件,webpack.base.conf.js
中是webpack通用的配置组成,例如入口文件,这绝对是通用的部分。然后我们再把区分的配置分别写到webpack.dev.conf.js
和 webpack.prod.conf.js
中,再通过webpack-merge
去合并配置。
在package.json
中配置webpack的调用方式,在script标签栏内填入
"scripts": {
"build": "webpack --config ./build/webpack.prod.conf.js",
"dev": "webpack-dev-server --config ./build/webpack.dev.conf.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
理所当然的,我们现在分成了两个指令:build负责打包生成产品文件,dev负责搭建热更新服务器,实时调试我们的代码。
所以,我们在webpack.dev.conf.js
中配webpack-dev-server
服务器。
在这里说明一下,为了保证大家安装的包版本跟我一致,安装依赖包的方式为直接在devDependencies中添加相关的版本,然后再调用cnpm install
.
找到package.json
添加依赖项
"webpack-dev-server": "^3.1.0",
"webpack-cli": "^3.2.3"
注意。webpack-dev-server依赖了"webpack-cli中的相关内容,所以也需要添加依赖。
在根目录
下执行cnpm install
.
完成之后,在webpack.dev.conf.js
中填入如下内容
const path = require('path');
const merge = require('webpack-merge');
const baseWebpackConfig = require('./webpack.base.conf')
module.exports = merge(baseWebpackConfig, {
devtool: 'cheap-module-eval-source-map',
output: {
filename: './dist/[hash]app.js', // dist文件夹不存在时,会自动创建
hashDigestLength: 8 // 默认长度是20
},
devServer: {
contentBase: path.join(__dirname, "../dist"), //网站的根目录为 根目录/dist,如果配置不对,会报Cannot GET /错误
port: 9000, //端口改为9000
open: true// 自动打开浏览器,适合懒人
},
})
webpack-merge
也是webpack的库,需要单独安装
找到package.json
添加依赖项
"webpack-merge": "^4.1.0"
在根目录
下执行cnpm install
.
webpack.dev.conf.js
中通过引用webpack.base.conf.js
,再通过merge对比,有的继承,没有的覆盖,这样就实现了公用基础配置的方法。devtool : cheap-module-eval-source-map
, devtool的配置比较复杂。这里你们先这样填,表示我可以在dev模式下F12 定位到原始文件debug我的代码。output
其实也是webpack基本配置。配置了项目打包生成的文件的存放处。由于我的dev模式和prod模式不一样,所以写在了dev的配置环境下。devserver
的配置参数我已经注释了。好了。现阶段用于dev
模式的webpack已经配置好了,接下来我们就可以开始编写我们的应用了。
使用React框架编写页面的方法有两种,第一种是直接在html文件中直接引用react.js和react-dom.js,然后在后续的脚本中使用React带来的能力。
一个普通的html文件便可以做到。
Hello world
但是这并不能很好的发挥react的作用。我们要使用ES6、JSX、UI重用等特性,因此我们将使用下面的第二种方法。
react是我们项目运行时需要用到的包,所以我们需要在package.json
中添加依赖项。react
最终会被打包进我们的app.js中,供应用程序运行。安装单页面react运行环境目前需要2个包 react
和react-dom
。我这里安装的是16.x的react(为了适配之后提到的antd):
//--package.json
"dependencies": {
"react": "^16.0.0",
"react-dom": "^16.0.0"
},
然后在根目录cnpm install
;
react安装好了。接下来我们就可以使用它了。在我们上面建立好的src/main.js
中填入按照上面的js修改后的如下内容:
//--main.js
import React from 'react';
import ReactDOM from 'react-dom';
var e = React.createElement('p', null, 'This is React');
ReactDOM.render(e, document.getElementById('main'));
这里我们引用了npm包中安装的react和react-dom。仔细一看,
ReactDOM.render(e, document.getElementById('main'));
噢、看来我们至少需要一个html作为父容器,毕竟没有dom,脚本不可能正常运行。
到这里我们发现,react跟Vue一样,需要一个html页面作为最大的容器,所有的内容最后都会被webpack各种loader编译为浏览器可执行的文件,放到html中。
这时候我们就需要webpack的一个插件html-webpack-plugin
,来生成html。首先我们需要安装他。这是在项目编译打包阶段需要使用的库,所以我们把他放在devDependencies
中。
//--package.json
"devDependencies": {
"webpack": "^4.0.0",
"webpack-cli": "^3.2.3",
"webpack-dev-server": "^3.1.0",
"webpack-merge": "^4.1.0",
"html-webpack-plugin": "^3.2.0",
}
根目录执行cnpm install
.
接下来我们要使用它。这个配置属于通用的基础配置,所以我们继续打开刚才的基础配置文件webpack.base.conf.js
,添加如下语句
//--webpack.base.conf.js
const HtmlWebpackPlugin = require('html-webpack-plugin'); //先引入
module.exports = {
entry: './src/main', //main.js中的.js可以省略,前面的./不能省
plugins: [
new HtmlWebpackPlugin()
]
}
现在服务器也好了这样我们应该就可以跑着看了。我们在根目录执行npm run dev
,没错,由于我们对于webpack-dev-server
的配置open:true
,在执行完毕之后我们就可以看到自动打开了浏览器,访问了我们配置的9000端口。
当然我们发现我们的浏览器一片空白
我们打开F12调试工具,先不管报的那个错误。我们去source栏查看,发现webpack确实已经帮我们根据入口main.js打包好了能运行的html。dist中是我们打包生成的app.js,并且已经根据我们配置在webpack.dev.conf.js
中的输出,即加上8位哈希值相匹配。但是这些文件都是在内存中生成的,本地并不能找到。生成的html中在body标签的下方引用了生成的js。
可以点开那个app.js查看一下。发现内部有我们配置在dependencies中的react已经被打包进去了。
但是,我们在main.js中的代码引用了html中的元素。然而这个html是webpack自动生成的。所以我们需要修改html-webpack-plugin
的配置,用我们自己的html作为模板。
webpack.base.conf.js
添加对于HtmlWebpackPlugin的配置。关于配置的详解我已经注释。const HtmlWebpackPlugin = require('html-webpack-plugin'); //第二步导入
module.exports = {
entry: './src/main', //main.js中的.js可以省略,前面的./不能省
plugins: [
new HtmlWebpackPlugin(
{
title: 'SEIE5.0', // html5文件中部分
filename: 'index.html', // 默认是index.html,服务器中设置的首页是index.html,如果这里改成其它名字,那么devServer.index改为和它一样,最终完整文件路径是output.path+filename,如果filename中有子文件夹形式,如`./ab/cd/front.html`,只取`./front.html`
template: './src/template/template.html', //如果觉得插件默认生成的hmtl5文件不合要求,可以指定一个模板,模板文件如果不存在,会报错,默认是在项目根目录下找模板文件,才模板为样板,将打包的js文件注入到body结尾处
inject: "body", // true|body|head|false,四种值,默认为true,true和body相同,是将js注入到body结束标签前,head将打包的js文件放在head结束前,false是不注入,这时得要手工在html中加js
}
)
]
}
src/template/template.html
添加如下内容:
Hello world
ok 现在我们已经配置了让webpack按照我们给定的模板作为根容器了。现在我们重新运行npm run dev
没错,已经完成了。现在react能够正常运行了。
注意。inject配置的注入一定要在body。如果填写了head的话,react会报一个错误。找不到dom元素。因为加载react的时候,dom还未初始化完毕。
到这里,你会发现不需要使用Babel,我们就可以使用React开发页面内容了。但是你会注意到,除了import语句是webpack做了兼容性处理的,我所使用的其他语法都是ES5的语法。
如果在main.js中使用JSX语法,webpack构建的时候就会报错。同样地,如果在main.js中添加ES6的语法,尽管webpack构建时不会报错,但生成的app.js文件末尾部分依旧可以找到这部分ES6语法的代码,这样的代码被用户加载到浏览器中执行,是否能被浏览器支持是无法得到保证的。
为此,我们需要引入Babel,让其把所有浏览器可能不支持的语法转换成ES5的语法。
使用Babel的方法也有好多种,官网的帮助文档可以根据环境提供对应的帮助。本文是基于Webpack使用Babel,因此无论是增加的依赖库还是配置流程均只对Webpack的场景有效。我们将使用Webpack关于Babel的一个扩展加载器babel-loader关联这两者。
package.json
添加安装依赖//--package.json
"devDependencies": {
...
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2"
},
上文提到,Babel用于把新版本的JS代码翻译成大多数浏览器都支持的ES5版本的代码。要做到这些翻译,只需要使用对应的扩展即可。例如:
根据官方的推荐,我们暂时先使用这几个:
"devDependencies": {
...
"babel-preset-env": "^1.6.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
},
所以bable的依赖包我们先安装5个。然后执行cnpm install
webpack.base.conf.js
,添加rules配置const HtmlWebpackPlugin = require('html-webpack-plugin'); //第二步导入
module.exports = {
entry: './src/main', //main.js中的.js可以省略,前面的./不能省
plugins: [
...
],
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['env', 'stage-0', 'react'],
plugins: []
}
}
}
]
}
}
这样我们就使用了bable-loader作为js的解析器。依次使用react、stage-0、env插件进行解析。
import React from 'react';
import ReactDOM from 'react-dom';
class Text extends React.Component {
render() {
return (
This is a react Component
);
}
}
ReactDOM.render( , document.getElementById('main'));
在根目录执行npm run dev
查看效果
没错。这下子我们完成了jsx语法的打包!Babel很好地工作了。
就跟iview一样。我们要的各种漂亮的组件不可能由我们自己去一个个实现,这在项目的开发周期限制下是不允许的。于是乎基于React的各种组件UI库相应而生,而我选择Ant Design
是因为其与iview风格、api及管网样式几乎一致。可以减少很多学习的时间。
在package.json
中添加ant-design依赖,由于是运行时依赖,所以需要加在dependencies
中
"dependencies": {
"antd": "^3.13.5",
"react": "^16.0.0",
"react-dom": "^16.0.0"
}
执行cnpm install
安装依赖。
好了,现在我们可以直接使用Ant Design的UI了。修改main.js,引入ant-design
的DatePicker
:
import React from 'react';
import ReactDOM from 'react-dom';
import { DatePicker } from 'antd';
class Text extends React.Component {
render() {
return (
This is a react Component
);
}
}
ReactDOM.render( , document.getElementById('main'));
执行npm run dev
,在浏览器中可以看到多了个日期选择器,但是没有相应的样式。
想要在js中引入css样式表,需要添加webpack的加载器,这样在js中引入的css样式将会被webpack构建成以动态方式插入到html文件中。
针对css样式,需要下面两个加载器:
我们在package.json
中安装如下依赖包
"devDependencies": {
...
"css-loader": "^2.1.0",
"style-loader": "^0.23.1"
},
执行cnpm install
安装依赖。
css-loader的配置属于通用的基本配置,所以我们打开刚才的webpack.base.conf.js
,添加css解析器:
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['env', 'stage-0', 'react'],
plugins: []
}
}
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
注意:因为antd的样式是放在node_modules中的,不要在css的loader中添加exclude命令刨除掉node_modules目录。
然后我们在main.js中引入antd的样式表:
//--main.js
...
import { DatePicker } from 'antd';
import 'antd/dist/antd.css'; // Add
...
再次执行热更新服务器查看效果。发现时间选择器已经有样式了。
不知道大家有没有发现现在页面的加载速度明显变慢了。这里是因为上述方法实际上加载整个antd包到最终生成的app.js文件中了。控制台也会看到相应的警告:
这里很明显,我们需要配置按需加载。配置按需加载需要使用另外一个Babel的插件babel-plugin-import
,在package.json
中添加引用:
"devDependencies": {
...
"babel-plugin-import": "^1.11.0"
...
}
执行cnpm install
安装依赖。
然后修改webpack.base.conf.js
中bable加载的方式,使用插件babel-plugin-import
:
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['env', 'stage-0', 'react'],
plugins: [['import', { "libraryName": "antd", "style": "css" }]]//--通过bable-plugin-import依赖 实现antd按需加载
}
}
}
]
}
}
大工告成。打包生成的app.js由13w行缩小到了5w行。注意,配置完按需加载之后,main.js
中就不需要在手动引入anti的css样式了,所以我们可以去掉这一行。发现时间选择器能够正常加载样式。
import ‘antd/dist/antd.css’;
less是比较主流的样式语法。我在项目中经常使用,同样的,如果你也想在该项目中使用less语法,那么就需要使用less-loader。
main.less
#main
{
.my-image{
width: 400px;
height: 500px;
}
}
main.js
中增加一个类名为my-image
的div元素,并且importmain.less
import React from 'react';
import ReactDOM from 'react-dom';
import { DatePicker } from 'antd';
import './styles/main.less'
// import 'antd/dist/antd.css'; // Add
class Text extends React.Component {
render() {
return (
This is a react Component123
);
}
}
ReactDOM.render( , document.getElementById('main'));
注意: react语法中class被替换为了className
当然,less是无法被webpack识别的。所以对应的,我们需要为less添加对应的加载器。这里我们就需要less-loader
了,当然,less-loader依赖less
package.json
中添加依赖://--package.json
"devDependencies": {
...
"less": "^2.7.3",
"less-loader": "^4.0.5",
...
}
执行cnpm install
安装依赖。
webpack.base.conf.js
中添加less后缀文件的加载器:...
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
}
...
这样配置是因为less-loader
依赖css-loader
,而css-loader
依赖于style-loader
;
运行服务器,发现样式表被正常应用到了html文件中。打开F12
调试工具,发现所有的样式被编译后转化为了样式表被放在了head
标签上:
我们在项目中必不可少的就是引用图片资源。对于图片资源,我们也需要使用对应的loader,来确保webpack对其能够进行正常的处理。为此我们需要引入url-loader
对于图片资源进行处理。
"devDependencies": {
...
"url-loader": "^1.1.2"
}
然后执行cnmp install
webpack.base.conf.js
中添加对应的解析器...
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'img/[name].[hash:7].[ext]'
}
}
...
表示对如上后缀资源的都采用url-loader处理。
limit
属性表示,当资源文件的大小没有超过该值时(单位:字节),编译过后统一转换为base64编码在项目中引用。超过之后,按照name
属性配置的目录生成在打包根目录
下,以命名规则进行命名。我们这里先设置为10000.
这时我们修改原先的main.less,将div设置一个一个背景图片
#main
{
.my-image{
width: 400px;
height: 500px;
background: url("../images/logo.png")
}
}
打开dev
服务器,发现图片能正常显示。打开F12
查看图片,发现图片被转为了base64编码:
然后我们再修改limit属性,将其设置为很小的数字 8.再次重启服务器,查看对比结果。
嗷。居然报错了~
原因很简单,因为图片大小超过了设定的大小,url-loader需要调用file-loader
进行文件复制和拷贝等操作,所以需要安装file-loader
//--package.json
"devDependencies": {
"file-loader": "^3.0.1"
}
执行cnpm install
安装后重新执行,重启服务器,得到正确的结果:
这时,dist下自动生成了img目录,将原有的图片拷贝了过来,并进行了重命名:
通过上面的例子我们发现,所有的样式都被放到了编译后的html的头部中,如果想将所有的样式统一打包到一个文件中,岂不是很美观。要做到这样,我们需要使用另外一个插件mini-css-extract-plugin
.其实在webpack3.x的时候,大家都是采用的extract-text-webpack-plugin
进行css的单独处理。但是在4.x,该插件并不支持,并且在4.x官网推荐使用的就是mini-css-extract-plugin
,所以我们今天就以该插件为例。
package.json
中安装依赖: "devDependencies": {
...
"mini-css-extract-plugin":"^0.5.0"
},
执行cnpm install
安装依赖。
webpack.base.conf.js
,先修改less文件的加载方式,将原先依赖的style-loader
替换为MiniCssExtractPlugin.loader
//--webpack.base.conf.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); //--注意需要引入
...
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.less$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
}
...
],
}
webpack.base.conf.js
的plugins
配置选项中声明使用该plugin并且添加其配置//--webpack.base.conf.js
plugins: [
new HtmlWebpackPlugin({
...
}),
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css"
})
]
没错!我们现在将css单独独立到了一个文件中了(暂时不管路径,会在下一节提及),并且查看html,在head
部分注入了对main.css的引用,原先在style
标签中引用的样式消失了。现在大家应该明白style-loader
的作用了吧。由于被替换为了MiniCssExtractPlugin.loader
,样式注入的流程被替代了。所以不会在html文件的style
标签中注入样式了。
依葫芦画瓢,我们把anti-design的样式也修改注入方式,全部放到main.css中。
//--webpack.base.conf.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); //--注意需要引入
...
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.less$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
}
...
],
}
重新运行npm run dev
。
完美。现在html中已经没有一大坨样式了,仅有对main.css
的引用打开main.css
,发现原来的样式都被放到了这里。
截止到上面为止,我们讨论的都是使用webpack-dev-server
构建服务器,然后在内存中加载对于webpack编译生成的文件,主要用于开发阶段使用的。虽然配置的写法都在webpack.base.conf.js
中,但是我们使用的指令npm run dev
使用的都是webpack.dev.conf.js
中的配置。当我们发布产品的时候,我们就需要一些不同于dev
的配置选项了。
webpack.prod.conf.js
,按照webpack.dev.conf.js
填写如下内容:const merge = require('webpack-merge');
const baseWebpackConfig = require('./webpack.base.conf')
module.exports = merge(baseWebpackConfig, {
})
npm run build
;发现我们的项目根目录下多了一个dist
文件夹,打开一看,哇塞。居然就是我们刚才所有在dev
模式下的生成的文件结构:
我们双击index.html
完美,浏览器中也能呈现和dev
模式下的一模一样的效果。注意,这里的所有资源都是可以被浏览器直接识别的,没有less,没有es6,也没有jsx等等,只有html,css,js。这就是编译发布的意义,这样所有的浏览器都能兼容运行我们的项目了。只需要一个服务器,将内容放上去托管,大家就可以访问我们的网页了。
到这里,其实你已经可以开始开发项目了。但是,还是有一些问题存在的,我们上面prod
的配置是完全使用base
的配置项。毕竟编译发布的配置和dev的配置还是有区别的。在这里,我列举几点问题和解决,其余的定制配置,还是需要大家自己去实现。只需要记得,在webpack.prod.conf.js
中配置编译发布环境,在webpack.dev.conf.js
中配置开发环境。在webpack.base.conf.js
中配置两者共用的环境即可。
webpack5可能会内置CSS 压缩器,webpack4需要自己使用压缩器,可以使用 optimize-css-assets-webpack-plugin
插件。 设置 optimization.minimizer
覆盖webpack默认提供的即可。
optimize-css-assets-webpack-plugin
"devDependencies": {
...
"optimize-css-assets-webpack-plugin": "^5.0.1",
...
},
执行cnpm install
webpack.prod.conf.js
://--webpack.prod.conf.js
const merge = require('webpack-merge');
const baseWebpackConfig = require('./webpack.base.conf')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = merge(baseWebpackConfig, {
optimization: {
minimizer: [
new OptimizeCSSAssetsPlugin({})
]
}
})
这里我们使用我们安装的插件,然后执行npm run build
即可。没错,这时我们发现我们的main.css
已经是压缩过的状态了~
不知道大家有没有发现。在执行上一步之前,我们调用npm run build
的时候,生成的main.js
都是默认压缩的。那是因为webpack4.x的新特性、webpack4新的Mode,且默认值是production,所以都是默认调用其内置的UglifyJsPlugin
进行压缩的。但是我们上一步复写了其默认配置。没有指定js压缩器,所以没有压缩js代码。这时候我们需要重新指定。
uglifyjs-webpack-plugin
"devDependencies": {
"uglifyjs-webpack-plugin": "^2.1.1"
},
执行cnpm install
安装依赖
webpack.prod.conf.js
修改optimization
配置:const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); //--注意需要引入
...
optimization: {
minimizer: [
new OptimizeCSSAssetsPlugin({}),
new UglifyJsPlugin({})
]
}
...
这时 我们重新执行npm run build
。ok js变成压缩过的版本了。这样可以打打减少我们项目打包出来文件的体积,提高网页加载的速度。
一般webpack打包出来的主流结构为一个static资源文件夹,然后是一个html文件。这样的结构合理,清晰。static文件夹内包含css,img,font等静态资源文件。这时候我们要修改我们项目的打包路径,使之符合这种审美。
我们先来看看我们现在打包生成的目录结构:
可以看到确实很丑。很乱,只有img文件夹是按照我们的想法的规则的。所以接下来我们要将其结构优化。
css
文件夹包裹:MiniCssExtractPlugin
,他是我们打包css最后一层调用的插件。于是我们修改他的配置://--webpack.base.conf.js
...
new MiniCssExtractPlugin({
filename: "css/[name].css",
chunkFilename: "[id].css"
})
因为官网说,filename
的配置方式和chunkFilename
的配置方式和webpack
完全一致,所以我们尝试修改生成css的路径。
保存之后,执行编译指令npm run build
。
没错,成功了,我们将main.css
打包进了css
文件夹下。
dev
和prod
模式均要使用。所以修改webpack.base.config.js
中配置output
优化js的生成目录,并且换个名叫app.js://--webpack.base.config.js
module.exports = merge(baseWebpackConfig, {
output: {
filename: 'js/app[hash].js', // dist文件夹不存在时,会自动创建
hashDigestLength: 8 // 默认长度是20
}
})
记得删除webpack.dev.config.js
中output
的配置,这样dev
模式才能继承到。
我们先运行npm run build
编译。
没错!现在js也被我们套了一层了。
static
文件夹这里我们需要了解一个webpack
的2个属性output.publicPath
和output.path
。output.path
该属性表示将所有资源打包进根目录下的对应路径内。而output.publicPath
表示html在引用这些资源的时候,默认在前面添加的路径。
第一步
修改一下output.publicPath
,由于这是基本配置,所以我们打开webpack.base.config.js
,添加如下语句
module.exports = {
...
output: {
filename: './js/app[hash].js', // dist文件夹不存在时,会自动创建
hashDigestLength: 8,// 默认长度是20
publicPath:"./static/"
}
...
}
执行一次npm run build
然后观察生成的html文件。
大家应该猜到了?就是这个意思,引用资源的前缀都会添加这个。我们的目的达到了。接下来就是把资源放到static
文件夹下。
第二步
还是上述文件webpack.base.config.js
我们再添加path
的配置
var path = require('path');//--path是node包中的类。注意引用
module.exports = {
...
output: {
filename: './js/app[hash].js', // dist文件夹不存在时,会自动创建
hashDigestLength: 8,// 默认长度是20
publicPath:"./static/",
path:path.resolve(__dirname, '../dist/static')
}
...
}
表示资源生成的路径为取相对路径的dist再拼上static。我们再编译一次。`npm run build
ok。完美。现在只剩下最后一步,将index.html
移到static
外,与其同级。
第三步
还是上述文件webpack.base.config.js
我们修改HtmlWebpackPlugin
的配置。将filename
属性修改为当前目录上一层:
...
plugins: [
new HtmlWebpackPlugin(
{
title: 'SEIE5.0', // html5文件中部分
filename: '../index.html',
template: './src/template/template.html',
inject: "body"
}
)
...
]
...
哈哈。大功告成。兴奋之余点击index.html查看效果,得意之余,突然发现引用的图片没了。没了。于是赶紧看控制台,果然。图片路径并不能正确找到。
那一行小字是引用路径,截图的原因看不太清楚:
file:///E:/github/webpack_test/dist/static/css/static/img/logo.3a54fdd.png
明显路径不对。本能的,由于请求是在css文件中发出的,所以我们去看打包生成的css文件。虽然被压缩过了,最后一行还是能找到:
原来是css-loader的问题。由于是相对路径,所以这个url大家都知道,会再css文件夹下查找图片。这当然是找不到的,应该在上一层。所以肯定是url-loader
的问题啦。于是我们打开url-loader
的配置项,添加publicPath
选项,让所有的图片引用都向上一层寻找,这跟webpack的publicPath
表示的是同一个意义:
打开webpack.base.config.js
...
module: {
rules: [
...
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 8,
name: 'img/[name].[hash:7].[ext]',
publicPath:"../"
}
},
],
},
...
执行npm run build
,再次打开浏览器查看最新的html。这次图片的路径就对了。
最后我们来验证下dev是否正常 npm run dev
服务器开启成功,页面加载成功,内存中生成的文件结构也是我们预先设定的结构:
执行npm run dev
。假如我们现在需要调试代码怎么办?app.js已经被打包的密密麻麻的,根本没办法打断点。这个时候我们就需要配置devtool
了。关于devtool
的详细配置和各参数的区别,可以参考这篇文章。
//-webpack.base.conf.js
module.exports = merge(baseWebpackConfig, {
...
devtool: '#source-map',
...
})
#source-map
表示对应的js生成对应的map映射文件,方便调试。这里还有一个坑我已经帮你们趟了。当我们使用UglifyJsPlugin
时,如果要使用source-map
,必须要指定sourceMap
为true;这是因为在webpack的官方文档中有下面一句话:
When using the uglifyjs-webpack-plugin you must provide the sourceMap: true option to enable SourceMap support.
也就是当使用了uglifyjs-webpack-plugin 插件时,sourceMap这个值的默认值是false,不开启map。如果要启用map,需要在插件中配置sourceMap值为true。
//--webpack.prod.config.js
const merge = require('webpack-merge');
const baseWebpackConfig = require('./webpack.base.conf')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = merge(baseWebpackConfig, {
devtool: '#source-map',
optimization: {
minimizer: [
new OptimizeCSSAssetsPlugin({}),
new UglifyJsPlugin({sourceMap: true})
]
}
})
我们运行npm run build
查看效果:
在js中生成了对应的map文件。
我们再执行npm run dev
打开f12 这时候按下Ctrl + P,发现我们可以打开源码中的main.js
,加上一个断点,刷新页面:
其实dev
模式和prod
模式应该采用不同的source-map以提高效率。这里我没有深入研究,希望懂的博友们可以留言给予帮助。
终于写完了。这篇文章写的很长时间,也是项目一边搭建,一边写的。期间遇到了不少问题,查过很多解决方案。不过一句古话。磨刀不误砍柴工,作为一个通用的模板项目,多付出些心血也是值得的。况且在写这篇博客的同时,也温习并且回顾了webpack很多的知识点。虽然这篇博客涉及的只是一小部分基础的功能,但是应对一般的项目已经绰绰有余了。
还有,如果大家在搭建项目的过程中发现什么问题或者疑问,欢迎留言评论,同时也欢迎大牛们的批评指正~
github项目地址 https://github.com/worlddai/webpack_react.git