初始化项目
$ npm install -g cnpm --registry=https://registry.npm.taobao.org // 安装cnpm
$ mkdir project // 根目录下创建一个名为project的文件夹
$ cd project && cnpm init // 进入project文件夹 并初始化该项目 终端会提示一些配置,一路键入enter最后yes即可。 初始化完成会生成一个pakage.json文件
安装webpack (4.x)
$ cnpm i --D webpack webpack-dev-server webpack-cli // webpack4.x 必须安装webpack-cli。
根目录下新建文件src/index.js
$ npm run build // 此时我们可以执行npm run build 代替npx webpack。 构建成功后根目录下自动生成 dist/main.js。
为什么我们没有对webpack进行任何配置,却可以成功打包?那是因为在没有配置入口文件的情况下, webpack 4.x会自动查找src/index.js作为入口文件进行打包。
webpack配置
这里我们需要了解webpack4个核心概念——入口(entry)、输出(output)、loader、插件(plugins)。
1.对js文件进行打包
根目录下创建webpack.config.js
const path = require("path");
module.exports = {
// 指定构建环境
mode:"development",
// 入口
entry: {
main: path.resolve(__dirname, './src/index.js') // 配置入口文件为src/index.js
},
// 出口
output: {
path : path.resolve(__dirname, "./dist"), // 出口文件放到dist文件夹下
filename: "[name].js", // 这里的[name] 取的是 entry里的main,如果entry里配置的为app: path.resolve(__dirname, './src/index.js') 则打包出的文件为dist/app.js
publicPath: "/" // 打包后的资源的访问路径前缀
},
// 模块
module:{
},
// 插件
plugins:[
],
// 开发环境本地启动的服务配置
devServer: {
}
}
$ cnpm run build
我们可以看到根目录下打包出了dist/main.js。
这时候如果我们将webpack.config.js中入口配置改为
entry: {
app: path.resolve(__dirname, './src/index.js')
}
$ cnpm run build
重新构建我们发下dist目录下多出了一个app.js文件,上一次打包的main.js还在,如果我们希望每次打包之前先清除掉上一次的打包文件,则需要用到clean-webpack-plugin插件。
$ cnpm i -D clean-webpack-plugin // vesion: 6.13.4 (6.x版本与低版本引入方式不一样)
安装后我们在webpack.config.js里进行配置 。
首先引入clean-webpack-plugin
再在plugins里实例化
然后我们将 entry: { app: {...}} 中的app改回main
$ cnpm run build
重新构建,我们可以看到上一次打包出的dist/app.js已经删掉了。
2.增加html文件
首先我们在dist目录下手动增加一个index.html文件,引入main.js
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>react-music</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
<div id="root"></div>
<script src="main.js"></script>
</body>
</html>
在chrome中打开该html文件,我们可以看到控制台中成功打印出hello world
那么问题来了,难道我们每次打包需要手动在dist目录下添加一个html文件吗? 显然不可能,这时候我们需要用到html-webpack-plugin插件自动添加html文件。
我们先将dist/index.html文件移动到src下,去掉<script>标签。
$ cnpm i -D html-webpack-plugin
安装好后一样先引入插件 具体插件配置参考官网
const HtmlWebpackPlugin = require('html-webpack-plugin');
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: 'src/index.html', // 自动生成的html文件以src/index.html为模板生成
inject: true, // true:默认值,script标签位于html文件的 body 底部
hash: true, // 在打包的资源插入html会加上hash
minify: {
removeComments: true, //去注释
collapseWhitespace: true, //压缩空格
removeAttributeQuotes: true //去除属性 标签的 引号 例如 <p id="test" /> 输出 <p id=test/>
}
}),
]
$ cnpm run build
此时我们看到dist目录下自动生成了一个index.html文件,里面包含了<div id="root"></div>,
这是因为我们在模板文件里写过。之前我们已经安装过了webpack-dev-server,并在package.json脚本里配置了"dev": "webpack-dev-server" 此时我们可以运行
$ cnpm run dev
终端提示自动运行在localhost:8080, chrome上打开8080端口,查看控制台,我们可以看到打印出了hello world。
3.丰富webpack-dev-server配置
// 开发环境本地启动的服务配置
devServer: {
historyApiFallback: true, // 当找不到路径的时候,默认加载index.html文件
hot: true,
contentBase: false, // 告诉服务器从哪里提供内容。只有在你想要提供静态文件时才需要
compress: true, // 一切服务都启用gzip 压缩:
port: "8989", // 指定端口号
publicPath: "/", // 访问资源加前缀
proxy: {
// 接口请求代理
},
}
$cnpm run dev
这时我们可以看到该项目已经运行在我们制定的端口8989上了。
打包css/less 、图片、字体等。
$ cnpm i -D style-loader css-loader less-loader file-loader url-loader
我们在src目录下新建一个images文件夹,然后拖入一张图片,这里图片名用了smokinggirl.jpeg。 再在src目录下新建一个index.less文件。
然后修改src/index.js src/index.less
import advatar from './images/smokinggirl.jpeg'; // 引入图片地址
import './index.less'; // 引入index.less
const root = document.getElementById('root');
const img = new Image();
img.src = advatar;
img.classList.add('advatar'); // 为图片增加类
root.appendChild(img);
src/index.less
@img-width: 200px;
.advatar {
width: @img-width;
}
webpack.config.js中module部分增加配置
module:{
rules: [{
test: /\.(jpg|jpeg|png|gif)$/, // 正则匹配文件后缀
use: {
loader: 'file-loader',
options: {
name: 'images/[name].[ext]'
}
}
}, {
test: /\.css$/,
use: ['style-loader', 'css-loader'],
}, {
test: /\.less$/,
loader: 'style-loader!css-loader!less-loader' // 等同于use: ['style-loader', 'css-loader', 'less-loader']
}]
},
$ cnpm run dev
http://localhost:8989 可以看到有一个宽200px的图片显示出来。
这样打包出来的css 样式会作用到全局,如果页面多可能会相互影响,这时候我们可以启用css模块,配置稍作修改
// 模块
module:{
rules: [{
test: /\.(jpg|jpeg|png|gif)$/,
use: {
loader: 'file-loader',
options: {
name: 'images/[name].[ext]'
}
}
}, {
test: /\.css$/,
exclude:/node_modules/,
use: [{
loader: 'style-loader',
}, {
loader: 'css-loader',
options: {
modules: true
}
}],
}, {
test: /\.less$/,
exclude:/node_modules/,
use: [{
loader: 'style-loader',
}, {
loader: 'css-loader',
options: {
modules: true,
}
}, {
loader: 'less-loader',
}]
}]
},
src/index.js
import advatar from './images/smokinggirl.jpeg'; // 引入图片地址
import styles from './index.less'; // 引入方式改变
const root = document.getElementById('root');
const img = new Image();
img.src = advatar;
img.classList.add(styles.advatar); // 从styles对象里获取类名
root.appendChild(img);
$cnpm run dev
这时候我们可以看到图片成功显示出来,但仔细观察图片类名,是一串自动生成的字符,与我们自己定义的类名没有半点关系,那么如何显示出我们定义的类名呢?
{
test: /\.less$/,
use: [{
loader: 'style-loader',
}, {
loader: 'css-loader',
options: {
modules: {
localIdentName: '[path][name]__[local]--[hash:base64:5]', // 这里可以自定义以什么样的形式打包出类名。
}
}
}, {
loader: 'less-loader',
}]
}
$ cnpm run dev 这时候可以看到打包出的类名符合我们定义的形式。
但仔细观察,我们发现body还有默认样式,我们可以在index.html引入一个公共样式文件,去除默认样式。
src目录下 新建common/reset-style.css, 写个简单的去默认样式的文件
body {
margin: 0;
padding: 0;
}
ul {
list-style: none;
margin: 0;
padding: 0;
}
然后在html模板文件 src/index.html中引入,
<head>
<meta charset="utf-8">
<title>project</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="../common/reset-style.css">
</head>
这时候我们运行,发现报错找不到该文件。这是因为我们在index.html里引入该文件,但webpack没有打包编译。这时候我们需要用到插件copy-webpack-plugin
$ cnpm i -D copy-webpack-plugin
const CopyWebpackPlugin = require('copy-webpack-plugin');
plugins:[
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: 'src/index.html',
inject: true, // true:默认值,script标签位于html文件的 body 底部
hash: true, // 在打包的资源插入html会加上hash
minify: {
removeComments: true, //去注释
collapseWhitespace: true, //压缩空格
removeAttributeQuotes: true //去除属性 标签的 引号 例如 <p id="test" /> 输出 <p id=test/>
}
}),
new CopyWebpackPlugin({
patterns: [
{
from: path.resolve(__dirname, './common'), // 从哪个目录copy
to: "common", // copy到那个目录
globOptions: {
ignore: ['.*'],
}
}
],
}),
],
重新运行,我们可以看到原先的默认样式body的边距已经没有了。
引入React
$ npm i -S react react-dom
安装完react、react-dom 还不够,我们需要babel做翻译,才能让浏览器读懂。
$ cnpm i -D babel-loader @babel/core @babel/preset-env @babel/preset-react @babel/plugin-transform-runtime @babel/runtime-corejs2
- @babel/preset-react 翻译react语法
- @babel/preset-env es6、es7转换成es5
- @babel/plugin-transform-runtime 对@babel/preset-env无法转换的es6、es7的新特性进行转换。
- @babel/runtime只能处理语法关键字,而@babel/runtime-corejs2还能处理新的全局变量(例如,Promise)、新的原生方法(例如,String.padStart );因此我们使用@babel/runtime-corejs2 就无须再使用 @babel/runtime了。
安装好后根目录新建.babelrc文件。
{
"presets": [
["@babel/preset-env", {
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}],
"@babel/preset-react"
],
"plugins": [
["@babel/plugin-transform-runtime",{
"corejs": 2, // polyfill 需要使用@babel/runtime-corejs2
"useBuildIns":"usage", //按需引入,即使用什么新特性打包什么新特性, 可以减小打包的体积
}]
]
}
webpack.config.js 的module里增加babel-loader的配置
{
test: /\.jsx?$/,//一个匹配loaders所处理的文件的拓展名的正则表达式,这里用来匹配js和jsx文件(必须)
exclude: /node_modules/,//屏蔽不需要处理的文件(文件夹)(可选)
loader: 'babel-loader',//loader的名称(必须) loader接收字符串, use接收对象或数组
}
配置好我们来写react文件
新建src/page/content/index.js, 将src下的index.less拖入content目录下。
import React, { Component } from 'react';
import advatar from '../../images/smokinggirl.jpeg'; // 引入图片地址
import styles from './index.less';
class Content extends Component {
render() {
return (
<div>
<h2>这是一张图片</h2>
<img src={advatar} className={styles.advatar} />
</div>
)
}
}
export default Content
接下来改造src/index.js 入口文件
import React, { PureComponent } from 'react';
import ReactDOM from 'react-dom';
import Content from './page/content';
class App extends PureComponent {
render() {
return (
<div>
<Content />
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'));
然后运行 我们可以看到页面正常显示。到这里我们已经可以正确打包并运行react项目了。
引入antd
$ cnpm i -S antd
根据antd官网描述,我们还需要在入口文件引入一个样式文件。
src/index.js
import 'antd/dist/antd.css';
到这里我们可以发现一个问题,我们对css文件打包的时候启用了模块,很明显这样直接引用是不行的。 antd的样式是从node_modules里引用的,那么我们的思路就是打包css文件时去除掉node_modules里面的文件再启用模块,node_modules里面的文件不启用模块。
接下来我们对webpack.config.json进行改造。
// 模块
module:{
rules: [{
test: /\.(jpg|jpeg|png|gif)$/,
use: {
loader: 'file-loader',
options: {
name: 'images/[name].[ext]'
}
}
}, {
test: /\.css$/,
exclude:/node_modules/,
use: [{
loader: 'style-loader',
}, {
loader: 'css-loader',
options: {
modules: {
localIdentName: '[path][name]__[local]--[hash:base64:5]',
}
}
}],
}, {
test: /\.css$/,
include: /node_modules/,
use: [{
loader: 'style-loader',
}, {
loader: 'css-loader',
options: {
modules: false,
}
}]
}, {
test: /\.less$/,
exclude:/node_modules/,
use: [{
loader: 'style-loader',
}, {
loader: 'css-loader',
options: {
modules: {
localIdentName: '[path][name]__[local]--[hash:base64:5]',
}
}
}, {
loader: 'less-loader',
}]
}, {
test: /\.jsx?$/,//一个匹配loaders所处理的文件的拓展名的正则表达式,这里用来匹配js和jsx文件(必须)
exclude: /node_modules/,//屏蔽不需要处理的文件(文件夹)(可选)
loader: 'babel-loader',//loader的名称(必须) loader接收字符串, use接收对象或数组
}]
},
只有这样还不够,我们需要用到babel-plugin-import插件实现按需加载,antd样式应用到全局需要用到此插件。
$cnpm i -D babel-plugin-import
.babelrc
"plugins": [
["@babel/plugin-transform-runtime",{
"corejs": 2, // polyfill 需要使用@babel/runtime-corejs2
"useBuildIns":"usage", //按需引入,即使用什么新特性打包什么新特性, 可以减小打包的体积
}],
["import", { // babel-plugin-import 按需加载插件 项目组件css使用modules方式打包,antd样式全局引用 需要用到此插件。
"libraryName":"antd",
"libraryDirectory":"es",
"style": "css"
}]
]
ok现在我们来改造一下src/page/content/index.js文件,引入一个antd组件。
import React, { Component } from 'react';
import { Radio } from 'antd';
import advatar from '../../images/smokinggirl.jpeg'; // 引入图片地址
import styles from './index.less';
class Content extends Component {
constructor(props) {
super(props);
this.state = {
type: 'img',
}
}
handleChange = e => {
const val = e.target.value;
this.setState({
type: val,
});
}
render() {
const { type } = this.state;
return (
<div>
<Radio.Group value={type} onChange={this.handleChange}>
<Radio value="img">图片</Radio>
<Radio value="text">文本</Radio>
</Radio.Group>
<h2>{type === 'img' ? '这是一张图片' : '这是一段文案'}</h2>
{
type === 'img'
?
<img src={advatar} className={styles.advatar} />
:
<p>这是一大段文案</p>
}
</div>
)
}
}
export default Content
现在我们来运行,发现报了个错
提示我们需要安装一个@babel/plugin-proposal-class-properties插件。
$ cnpm i -D @babel/plugin-proposal-class-properties
然后我们需要在babel的配置文件中加入它
"plugins": [
["@babel/plugin-transform-runtime",{
"corejs": 2, // polyfill 需要使用@babel/runtime-corejs2
"useBuildIns":"usage", //按需引入,即使用什么新特性打包什么新特性, 可以减小打包的体积
}],
["import", { // babel-plugin-import 按需加载插件 项目组件css使用modules方式打包,antd样式全局引用 需要用到此插件。
"libraryName":"antd",
"libraryDirectory":"es",
"style": "css"
}],
["@babel/plugin-proposal-class-properties"]
]
现在我们重新运行 $ cnpm run dev 一个简单的包含antd组件的页面就完成了。
。