初始化項目
$ npm install -g cnpm --registry=https://registry.npm.taobao.org // 安裝cnpm
$ mkdir project // 根目錄下創(chuàng)建一個名為project的文件夾
$ cd project && cnpm init // 進(jìn)入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 // 此時我們可以執(zhí)行npm run build 代替npx webpack涝滴。 構(gòu)建成功后根目錄下自動生成 dist/main.js雷逆。
為什么我們沒有對webpack進(jìn)行任何配置骇两,卻可以成功打包寿酌?那是因為在沒有配置入口文件的情況下掉瞳, webpack 4.x會自動查找src/index.js作為入口文件進(jìn)行打包饮醇。
webpack配置
這里我們需要了解webpack4個核心概念——入口(entry)入桂、輸出(output)、loader驳阎、插件(plugins)抗愁。
1.對js文件進(jìn)行打包
根目錄下創(chuàng)建webpack.config.js
const path = require("path");
module.exports = {
// 指定構(gòu)建環(huán)境
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:[
],
// 開發(fā)環(huán)境本地啟動的服務(wù)配置
devServer: {
}
}
$ cnpm run build
我們可以看到根目錄下打包出了dist/main.js呵晚。
這時候如果我們將webpack.config.js中入口配置改為
entry: {
app: path.resolve(__dirname, './src/index.js')
}
$ cnpm run build
重新構(gòu)建我們發(fā)下dist目錄下多出了一個app.js文件蜘腌,上一次打包的main.js還在,如果我們希望每次打包之前先清除掉上一次的打包文件饵隙,則需要用到clean-webpack-plugin插件撮珠。
$ cnpm i -D clean-webpack-plugin // vesion: 6.13.4 (6.x版本與低版本引入方式不一樣)
安裝后我們在webpack.config.js里進(jìn)行配置 。
首先引入clean-webpack-plugin
再在plugins里實例化
然后我們將 entry: { app: {...}} 中的app改回main
$ cnpm run build
重新構(gòu)建金矛,我們可以看到上一次打包出的dist/app.js已經(jīng)刪掉了芯急。
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>標(biāo)簽。
$ cnpm i -D html-webpack-plugin
安裝好后一樣先引入插件 具體插件配置參考官網(wǎng)
const HtmlWebpackPlugin = require('html-webpack-plugin');
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: 'src/index.html', // 自動生成的html文件以src/index.html為模板生成
inject: true, // true:默認(rèn)值想鹰,script標(biāo)簽位于html文件的 body 底部
hash: true, // 在打包的資源插入html會加上hash
minify: {
removeComments: true, //去注釋
collapseWhitespace: true, //壓縮空格
removeAttributeQuotes: true //去除屬性 標(biāo)簽的 引號 例如 <p id="test" /> 輸出 <p id=test/>
}
}),
]
$ cnpm run build
此時我們看到dist目錄下自動生成了一個index.html文件紊婉,里面包含了<div id="root"></div>,
這是因為我們在模板文件里寫過。之前我們已經(jīng)安裝過了webpack-dev-server辑舷,并在package.json腳本里配置了"dev": "webpack-dev-server" 此時我們可以運行
$ cnpm run dev
終端提示自動運行在localhost:8080, chrome上打開8080端口喻犁,查看控制臺,我們可以看到打印出了hello world何缓。
3.豐富webpack-dev-server配置
// 開發(fā)環(huán)境本地啟動的服務(wù)配置
devServer: {
historyApiFallback: true, // 當(dāng)找不到路徑的時候株汉,默認(rèn)加載index.html文件
hot: true,
contentBase: false, // 告訴服務(wù)器從哪里提供內(nèi)容。只有在你想要提供靜態(tài)文件時才需要
compress: true, // 一切服務(wù)都啟用gzip 壓縮:
port: "8989", // 指定端口號
publicPath: "/", // 訪問資源加前綴
proxy: {
// 接口請求代理
},
}
$cnpm run dev
這時我們可以看到該項目已經(jīng)運行在我們制定的端口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
這時候我們可以看到圖片成功顯示出來隙轻,但仔細(xì)觀察圖片類名埠帕,是一串自動生成的字符,與我們自己定義的類名沒有半點關(guān)系玖绿,那么如何顯示出我們定義的類名呢敛瓷?
{
test: /\.less$/,
use: [{
loader: 'style-loader',
}, {
loader: 'css-loader',
options: {
modules: {
localIdentName: '[path][name]__[local]--[hash:base64:5]', // 這里可以自定義以什么樣的形式打包出類名。
}
}
}, {
loader: 'less-loader',
}]
}
$ cnpm run dev 這時候可以看到打包出的類名符合我們定義的形式斑匪。
但仔細(xì)觀察呐籽,我們發(fā)現(xiàn)body還有默認(rèn)樣式,我們可以在index.html引入一個公共樣式文件,去除默認(rèn)樣式狡蝶。
src目錄下 新建common/reset-style.css, 寫個簡單的去默認(rèn)樣式的文件
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>
這時候我們運行,發(fā)現(xiàn)報錯找不到該文件贪惹。這是因為我們在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:默認(rèn)值馍乙,script標(biāo)簽位于html文件的 body 底部
hash: true, // 在打包的資源插入html會加上hash
minify: {
removeComments: true, //去注釋
collapseWhitespace: true, //壓縮空格
removeAttributeQuotes: true //去除屬性 標(biāo)簽的 引號 例如 <p id="test" /> 輸出 <p id=test/>
}
}),
new CopyWebpackPlugin({
patterns: [
{
from: path.resolve(__dirname, './common'), // 從哪個目錄copy
to: "common", // copy到那個目錄
globOptions: {
ignore: ['.*'],
}
}
],
}),
],
重新運行,我們可以看到原先的默認(rèn)樣式body的邊距已經(jīng)沒有了垫释。
引入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轉(zhuǎn)換成es5
- @babel/plugin-transform-runtime 對@babel/preset-env無法轉(zhuǎn)換的es6订咸、es7的新特性進(jìn)行轉(zhuǎn)換曼尊。
- @babel/runtime只能處理語法關(guān)鍵字,而@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所處理的文件的拓展名的正則表達(dá)式趾唱,這里用來匹配js和jsx文件(必須)
exclude: /node_modules/,//屏蔽不需要處理的文件(文件夾)(可選)
loader: 'babel-loader',//loader的名稱(必須) loader接收字符串涌乳, use接收對象或數(shù)組
}
配置好我們來寫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'));
然后運行 我們可以看到頁面正常顯示甜癞。到這里我們已經(jīng)可以正確打包并運行react項目了夕晓。
引入antd
$ cnpm i -S antd
根據(jù)antd官網(wǎng)描述,我們還需要在入口文件引入一個樣式文件。
src/index.js
import 'antd/dist/antd.css';
到這里我們可以發(fā)現(xiàn)一個問題,我們對css文件打包的時候啟用了模塊峭状,很明顯這樣直接引用是不行的茬斧。 antd的樣式是從node_modules里引用的,那么我們的思路就是打包css文件時去除掉node_modules里面的文件再啟用模塊吱晒,node_modules里面的文件不啟用模塊。
接下來我們對webpack.config.json進(jìn)行改造。
// 模塊
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所處理的文件的拓展名的正則表達(dá)式逗宜,這里用來匹配js和jsx文件(必須)
exclude: /node_modules/,//屏蔽不需要處理的文件(文件夾)(可選)
loader: 'babel-loader',//loader的名稱(必須) loader接收字符串, use接收對象或數(shù)組
}]
},
只有這樣還不夠,我們需要用到babel-plugin-import插件實現(xiàn)按需加載纺讲,antd樣式應(yīng)用到全局需要用到此插件擂仍。
$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現(xiàn)在我們來改造一下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
現(xiàn)在我們來運行乡括,發(fā)現(xiàn)報了個錯
提示我們需要安裝一個@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"]
]
現(xiàn)在我們重新運行 $ cnpm run dev 一個簡單的包含antd組件的頁面就完成了盲赊。
。