webpack-02 - 使用webpack从零开始构建一个react项目

254 阅读5分钟

新建个项目

  1. npm init -y
    
  2. npm i webpack@4.43.0 webpack-cli -D
    
  3. 项目根目录中创建一个 src文件夹,src文件夹下新建index.jsindex.html文件

  4. 执行npx webpack测试。

  5. 下载

    // html-webpack-plugin的版本要与webpack的版本一致,例如webpack用的是4,那么html-webpack-plugin版本也要是4
    // 否则会报错:html-webpack-plugin中 Cannot read property 'tap' of undefined
    npm i html-webpack-plugin@4.3.0 clean-webpack-plugin -D
    
  6. 修改配置

    // 创建webpack.config.js文件
    const path = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    
    module.exports = {
        entry: { index: './src/index' },
        output: {
            path: path.resolve(__dirname, './dist'),
            filename: '[name].js'
        },
        mode: 'development',
        module: {
            rules: []
        },
        devtool: 'cheap-source-map',
        plugins: [
            new HtmlWebpackPlugin({
                template: './src/index.html',
                filename: 'index.html',
                chunks: ['index']
            }), new CleanWebpackPlugin()
        ]
    }
    
  7. 添加样式

    css:

    // src下新建个styles/index.css
    body {
        background-color: pink;
    }
    
    // 下载loader
    npm i style-loader css-loader -D
    
    // 修改webpack.config.js文件中的module
        module: {
            rules: [
                {
                    test: /\.less$/,
                    use: ['style-loader', 'css-loader']
                }
            ]
        }
    
    

    less:

       // src下新建个styles/index.less
       html{
           body {
               display: flex;
               background-color: pink;
           }
       }
       // index.js中引入less
       import './styles/index.less';
    
       // 下载less-loader (最新版本不兼容 指定一个版本下载)
       // less-laoder是webpack沟通less编译器的桥梁 所以我们还需要安装less来转换为css文件
       npm install less less-loader@5.0.0 -D
       // 修改webpack.config.js文件中的module
           module: {
               rules: [
                   {
                       test: /\.less$/,
                       use: ['style-loader', 'css-loader', 'less-loader']
                }
               ]
           }
    

后处理器postcss

为了使css3语法被更多浏览器所兼容,也就是说给样式⾃动添加前缀。

// 最新版本不兼容 指定一个版本下载
// 不需要安装postcss了,因为之前我们安装css-loader依赖包依赖它,所以就已经安装了
npm i postcss-loader@3.0.0 autoprefixer@9.8.4 -D
// 修改webpack.config.js文件中的module
 module: {
     rules: [
         {
             test: /\.less$/,
             use: ['style-loader', 'css-loader', "postcss-loader", 'less-loader']
         }
     ]
 }
// 新建postcss.config.js文件
module.exports = {
 //postcs就是处理css成AST抽象语法树
 //如何把这个AST抽象语法树转换成相应的css =>  postcss插件机制
 plugins: [
   require("autoprefixer")({
     overrideBrowserslist: ["last 3 versions", ">2%"], 
     // 配置参数 降低浏览器的标准,因为浏览器很多属性已经支持了
     // browserslist: package.json中的一个配置字段,浏览器的类型,告诉项目是兼容到哪些浏览器的
     // overrideBrowserslist: override是重写、覆盖的意思,这样它的优先级就比browserslist高
     // ["last 3 versions", ">2%"]:是 last 2 versions是兼容到浏览器的最后(最新)三个版本,>2%是浏览器在市面上的占有率大于2%的都要兼容到
   }),
 ],
};

静态资源

图片

file-loader

处理静态资源模块。原理是把打包⼊⼝中识别出的资源模块,移动到输出⽬录,并且返回⼀个地址名称。什么时候⽤file-loader呢?场景:就是当我们需要模块,仅仅是从源代码挪移到打包⽬录,不需要对文件的内容进行加工的,就可以使⽤file-loader来处理,txt,md,png,word,pdf, svg,csv,excel等等。

npm i file-loader -D

配置:

{
    test: /\.(jpg|png|jpeg)$/,
    use: {
        loader: 'file-loader',
        options: {
            // [name]: 之前的图片名字
            // [hash:4]: [hash]是本次打包的hash值,[hash:4]是截取4位hash值
            // [ext]是之前图片的后缀
            name: "[name]_[hash:4].[ext]"
        }
    }
}

使用:

  • index.html中添加: <p></p>

  • src下新建个imgs文件夹,放入一张图片

  • index.less中:

p {
   background: url('../imgs/1.jpg');
   width: 200px;
   height: 200px;
}
url-loader

内部使⽤了file-loader,所以可以处理file-loader所有的事情,但是遇到jpg格式的模块,会把该图⽚转换成base64格式字符串,并打包到js⾥。对⼩体积的图⽚⽐较合适,⼤图⽚不合适。

npm i url-loader -D
{
    test: /\.(jpg|png|jpeg)$/,
    use: {
        loader: 'url-loader',
        options: {
            name: "[name]_[hash:4].[ext]",
            outputPath: 'imgs/', 
            limit: 20480, // ⼩于20480,才转换成base64,可以帮助我们处理小体积的图片,减少http请求
        }
    }
}

字体font

  • 前往阿里iconfont官网,找到对应的图标,下载代码。

  • 在项目中新建个fonts文件,找到下载代码中的iconfont.woff2文件,,放入fonts文件夹中。

  • 在index.less中加入字体的css

  例如:
  @font-face {
      font-family: 'iconfont';
      src: url('../fonts/iconfont.woff2') format('woff'),
    }
    .iconfont {
      font-family: "iconfont" !important;
      font-size: 24px;
      font-style: normal;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
    }
  • 在index.html中加入对应的标签

    <span class="iconfont">&#xe61b;</span>
    
  • 修改配置

    {
        test: /\.(ttf|woff|woff2|svg)$/,
        use: {
            loader: 'file-loader',
            options: {
                name: "[name]_[hash:4].[ext]",
                outputPath: 'fonts/', 
            }
        }
    }
    
  • 重新打包

多页面常用打包配置

借助glob来匹配路径。

先安装:

npm i glob -D

在src下新建个views文件夹用于存放我们的页面,然后在里面新建login文件夹,文件夹中新建index.html、index.js文件。如果还有其他页面,只需要保证文件夹内文件名字是index.html、index.js即可。

修改配置文件增加以下代码:

const glob = require('glob');

const setEntryAndPlugin = () => {
    const entry = {};
    const htmlWebpackPlugins = [];
    const entryFiles = glob.sync(path.join(__dirname, './src/views/*/index.js'));
    entryFiles.forEach(item => {
        const match = item.match(/src\/views\/(.*)\/index.js/);
        const pageName = match && match[1];
        entry[pageName] = item;
        htmlWebpackPlugins.push(new HtmlWebpackPlugin({
            template: `./src/views/${pageName}/index.html`,
            filename: `${pageName}.html`,
            chunks: [pageName]
        }))
    })
    return {
        htmlWebpackPlugins,
        entry
    }
}
const {entry, htmlWebpackPlugins} = setEntryAndPlugin();



module.exports = {
	entry: {
        index: './src/index',
        ...entry,
    },
	xxxxxx,
	plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html',
            filename: 'index.html',
            chunks: ['index']
        }),
        ...htmlWebpackPlugins,
        new CleanWebpackPlugin()
    ]
}

请求

下载express和axios以及mock。

npm i express axios mockjs -D

新建个serve/serve.js文件,写上代码:

const express = require('express');
const app = express();
const Mock = require('mockjs');

app.get('/apis/getUser', (req, res) => {
   const data =  Mock.mock({
        'list|1-10': [{
            'id|+1': 1,
            'name': '@name',
            'age|10-40': 1
        }],
        
    });
    res.send(data);
});
app.listen('3001');

在终端中找到这个文件,执行命令:node serve.js启动这个服务。

测试:

在页面上打开: http://localhost:3001/apis/getUser查看有无数据。

在index.js中,发送请求:

import axios from 'axios';

axios.get('http://localhost:3001/apis/getUser').then(res => {
    console.log(res);
})

然后我们就看到跨域了,因为不同源。

解决方法:使用WebpackDevServer用法及注意点:

修改配置:

devServer: {
    contentBase: path.resolve(__dirname, '../dist'), // 默认会以根文件夹提供本地服务器,这里指定文件夹(指向静态资源目录)
    inline: true, // 自动刷新
    hot: true, // 开启热模块替换
    historyApiFallback: true, // 在开发单页应用时非常有用,它依赖于HTML5 history API,如果设置为true,所有的跳转将指向index.html
    open: true, // 服务启动之后,会自动帮我们打开浏览器窗口
    publicPath: '/',
    compress: true, // 使用gzip压缩
    stats: 'minimal',
    port: 8080, // 端口号,默认是8080
    proxy: { // 代理
        "/apis": {
            target: 'http://localhost:3001'
        }
    }
},

然后请求修改为:

axios.get('/apis/getUser').then(res => {
    console.log(res);
})

启动服务器,查看请求。

babel

官⽅⽹站:babeljs.io/

中⽂⽹站:www.babeljs.cn/

Babel是JavaScript编译器,能将ES6代码转换成ES5代码,让我们开发过程中放⼼使⽤JS新特性⽽不⽤担⼼兼容性问题。并且还可以通过插件机制根据需求灵活的扩展。

Babel在执⾏编译的过程中,会从项⽬根⽬录下的 .babelrc JSON⽂件中读取配置。没有该⽂件会从loader的options地⽅读取配置。

安装:

npm i babel-loader @babel/core @babel/preset-env core-js -D
  • @babel/preset-env⾥包含了es,6,7,8转es5的转换规则
  • babel-loader是webpack 与 babel的通信桥梁,不会做把es6转成es5的⼯作,这部分⼯作需要⽤到@babel/preset-env来做
  • env是babel7之后推⾏的预设插件

通过上⾯的⼏步 还不够,默认的Babel只⽀持let等⼀些基础的特性转换,Promise等⼀些还有转换过来,这时候需要借助@babel/polyfill,把es的新特性都装进来,来弥补低版本浏览器中缺失的特性。

npm i @babel/polyfill -D

根目录下新建个.babelrc文件,把配置项都放入到这个文件中。

{
    "presets": [
        [
            "@babel/preset-env",
            {
                "useBuiltIns": "usage", // 按需加载:但是不需要import ,全⾃动检测
                "corejs": 3 // /新版本需要指定核⼼库版本
            }
        ]
    ]
}

增加一个rules:

{
    test: /\.(js|jsx)$/, // js或者jsx文件都可以
    exclude: /node_modules/,
    use: {
        loader: "babel-loader",
    }
}

测试: index.js中

new Promise((resolve, reject) => {
    axios.get('/apis/getUser').then(res => {
        resolve(res);
    }).catch(err => {
        reject(err);
    })
}).then(({ data: { list } }) => {
    console.log(list);
})

配置react

安装:

npm i react react-dom -D
// babel与react转换的插件
npm i @babel/preset-react -D 

但是就这样的话,我们没办法在class类中,使用state = {},函数名 = ()=> {}等等方法,怎么办呢?解决方案:@babel/plugin-proposal-class-properties

官方地址

// 修改.babelrc文件增加plugins
{
    "presets": [
        [
            "@babel/preset-env",
            {
                "useBuiltIns": "usage",
                "corejs": 3
            }
        ],
        "@babel/preset-react"
    ],
    "plugins": [ // 配置class类中,可以使用state = {},函数名 = ()=> {}, 地址: https://babeljs.io/docs/en/babel-plugin-proposal-class-properties
        [
            "@babel/plugin-proposal-class-properties",
            {
                "loose": true
            }
        ]
    ]
}

测试:

index.html中添加个宿主容器:<div id="root"></div>

index.js(jsx)中添加:

import React, { Component } from "react";
import ReactDom from "react-dom";
import axios from 'axios';

class Home extends Component {
    state = {
        count: 1,
        list: []
    }
    btnclick = (num) => {
        this.setState({
            count: this.state.count + num
        });
    }
    componentDidMount(){
        axios.get('/apis/getUser').then(res => {
            const {data: {list}} = res;
            this.setState({
                list
            });
        });
    }
    
    render() {
        const { list, count } = this.state;
        return <div>
            <button onClick={() => this.btnclick(1)}>加</button>
            <span>{count}</span>
            <button onClick={() => this.btnclick(-1)}>减</button>
            {
                list.map(item => <p key={item.id}>{item.id} - {item.name} - {item.age}</p>)
            }
        </div>
    }
}
ReactDom.render(<Home />, document.getElementById('root'));

代码地址

github代码地址: