使用webpack4从0开始打包一个antd项目

初始化项目

$ 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

image

$ 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


截屏2020-06-13 下午9.28.13.png

重新构建我们发下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


[图片上传中...(截屏2020-06-13 下午9.33.58.png-d29026-1592055241921-0)]

再在plugins里实例化


截屏2020-06-13 下午9.34.40.png

然后我们将 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


截屏2020-06-13 下午9.45.25.png

那么问题来了,难道我们每次打包需要手动在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


截屏2020-06-14 上午9.48.49.png

这时我们可以看到该项目已经运行在我们制定的端口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
这时候我们可以看到图片成功显示出来,但仔细观察图片类名,是一串自动生成的字符,与我们自己定义的类名没有半点关系,那么如何显示出我们定义的类名呢?


截屏2020-06-14 上午11.18.35.png
   {
        test: /\.less$/,
        use: [{
          loader: 'style-loader',
        }, {
          loader: 'css-loader',
          options: {
            modules: {
              localIdentName: '[path][name]__[local]--[hash:base64:5]',  // 这里可以自定义以什么样的形式打包出类名。
            }
          }
        }, {
          loader: 'less-loader',
        }]
   }

$ cnpm run dev 这时候可以看到打包出的类名符合我们定义的形式。


截屏2020-06-14 上午11.24.37.png

但仔细观察,我们发现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

现在我们来运行,发现报了个错


截屏2020-06-14 下午3.35.50.png

提示我们需要安装一个@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组件的页面就完成了。


截屏2020-06-14 下午3.38.46.png

截屏2020-06-14 下午3.39.14.png

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349