从零到一:手把手搭建现代化 Webpack 5 项目

起步

首先安装 webpack 依赖

bach 复制代码
npm init -y
npm install webpack webpack-cli -D

我们要使用 ts 开发项目

bash 复制代码
tsc --init

配置打包命令,同时需要创建一个 webpack.config.ts 文件

json 复制代码
{
  "scripts": {
    "build": "webpack --config webpack.config.ts"
  }
}

webpack 默认不支持直接使用 ts 格式的配置文件,需要安装 ts-node

bash 复制代码
npm i ts-node -D

使 webpack 支持 ts 格式的配置文件,需要安装 ts-loader

bash 复制代码
npm i ts-loader -D

编写 webpack 配置文件

ts 复制代码
// webpack.config.ts
import type { Configuration } from 'webpack'
import path from 'path'

const config: Configuration = {
  entry: './src/main.ts',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, './dist'),
  },
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: ['.ts', '.js'],
  },
}

export default config

创建 webpack 入口文件 src/main.ts

ts 复制代码
// src/main.ts
console.log('hello world')

最终文件目录如下

import { FileTree } from 'nextra/components'

执行打包命令,会在 dist 目录下生成 bundle.js 文件

bash 复制代码
npm run build

最基本的 webpack 配置就完成了,接下来我们可以继续配置一些其他的插件,来提高开发效率。

html-webpack-plugin

html-webpack-plugin 是 webpack 生态中一个非常核心的插件,它的主要作用是自动化生成 html 文件,并自动注入 webpack 打包后的资源(如 jscss 文件)。

bash 复制代码
npm i --D html-webpack-plugin
ts 复制代码
// webpack.config.ts
import type { Configuration } from 'webpack'
import path from 'path'
import HtmlWebpackPlugin from 'html-webpack-plugin'

const config: Configuration = {
  entry: './src/main.ts',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, './dist'),
  },
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: ['.ts', '.js'],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, 'public/index.html'),
    }),
  ],
}

export default config

执行 npm run build 后,会在 dist 目录下生成 index.html 文件,同时会自动注入 bundle.js 文件。

dev-server

webpack-dev-server 是一个开发服务器,它可以帮助我们在开发过程中实时预览项目效果。

bash 复制代码
npm i --D webpack-dev-server
json 复制代码
{
  "scripts": {
    "dev": "webpack serve --config webpack.config.ts"
  }
}
ts 复制代码
const config: Configuration = {
  devServer: {
    static: './dist',
    port: 8080,
  },
}

加载资源

加载 css

JavaScript 模块中导入 css 文件,需要使用 style-loadercss-loader

bash 复制代码
npm i style-loader css-loader -D
ts 复制代码
const config: Configuration = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
}

如果要加载 sass 文件,需要安装 sass-loadersass

bash 复制代码
npm i sass-loader sass -D

将 sass-loader 、css-loader 与 style-loader 进行链式调用, 使得 webpack 能够正确解析 sass 文件。

ts 复制代码
const config: Configuration = {
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: ['style-loader', 'css-loader', 'sass-loader'],
      },
    ],
  },
}

图片资源

webpack 5 自带了处理图片资源的功能,只需要配置 asset/resource 即可

ts 复制代码
const config: Configuration = {
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif|svg)$/i,
        type: 'asset/resource',
      },
    ],
  },
}

字体资源

ts 复制代码
const config: Configuration = {
  module: {
    rules: [
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
      },
    ],
  },
}

Vue

使用 vue-loader 来加载 vue 文件,这里我们还需要安装 vue-template-compiler,因为 vue-loader 依赖于 vue-template-compiler

bash 复制代码
npm i vue vue-loader vue-template-compiler -D

这里我们需要安装 vue-style-loader 来加载 vue 文件中的样式,替换掉之前的 style-loader

bash 复制代码
npm i vue-style-loader -D
ts 复制代码
import { VueLoaderPlugin } from 'vue-loader'

const config: Configuration = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['vue-style-loader', 'css-loader'],
      },
      {
        test: /\.scss$/,
        use: ['vue-style-loader', 'css-loader', 'sass-loader'],
      },
      {
        test: /\.vue$/,
        use: 'vue-loader',
      },
    ],
  },
  plugins: [new VueLoaderPlugin()],
}

创建一个 App.vue 文件,来测试 vue 文件的加载

vue 复制代码
<script lang="ts" setup>
const msg = 'Hello, Vue 3.0 + TypeScript + Webpack!'
</script>
<template>
  <div>{{ msg }}</div>
</template>

修改 main.ts 文件

ts 复制代码
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
app.mount('#app')

我们需要创建 shims.d.ts 文件,来声明 .vue 文件的模块

ts 复制代码
// src/shims-vue.d.ts
declare module '*.vue' {
  import type { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}

页面上就会显示

复制代码
Hello, Vue 3.0 + TypeScript + Webpack!

环境变量

安装 cross-env 用于设置环境变量

bash 复制代码
npm i cross-env -D

我们希望 webpack 支持不同环境的配置,比如开发环境、测试环境、生产环境等。支持使用 .env 文件来配置环境变量。

bash 复制代码
npm i dotenv -D

解析 .env 文件

ts 复制代码
import dotenv from 'dotenv'
import fs from 'fs'

const envFiles = [
  path.resolve(__dirname, '.env'),
  path.resolve(__dirname, isDev ? '.env.development' : '.env.production'),
  path.resolve(__dirname, '.env.local'),
]
const env = envFiles.reduce((acc, envFile) => {
  if (fs.existsSync(envFile)) {
    const result = dotenv.config({ path: envFile })
    if (result.error) {
      //
    } else {
      Object.assign(acc, result.parsed)
    }
  } else {
    console.warn(`File not found: ${envFile}`)
  }
  return acc
}, {})

在 webpack 配置文件中使用环境变量

ts 复制代码
const config: Configuration = {
  plugins: [
    new webpack.DefinePlugin({
      'process.env': JSON.stringify(env),
    }),
  ],
}

这样,我们在代码中就可以使用 process.env 来访问环境变量了。

不同环境配置

在不同环境下,我们可能需要不同的配置,比如开发环境下需要启用 source-map,生产环境下需要压缩代码等。我们需要将webpack配置文件拆分成不同的文件,然后根据环境变量来加载不同的配置文件。

在这之前需要安装 webpack-merge 来合并配置文件

bash 复制代码
npm i webpack-merge -D

可以将 webpack 配置文件拆分成 webpack.config.tswebpack.config.dev.tswebpack.config.prod.ts 三个文件

ts 复制代码
import { Configuration } from 'webpack'
import merge from 'webpack-merge'
import baseConfig from './webpack.config'

const devConfig: Configuration = merge(baseConfig, {
  mode: 'development',
  devtool: 'source-map',
  devServer: {
    port: process.env.PORT ? process.env.PORT : 8080,
    static: './dist',
  },
})
export default devConfig
ts 复制代码
import { Configuration } from 'webpack'
import merge from 'webpack-merge'
import baseConfig from './webpack.config'

export const prodConfig: Configuration = merge(baseConfig, {
  mode: 'production',
  optimization: {
    minimize: true,
    splitChunks: {
      chunks: 'all',
    },
  },
})
export default prodConfig

eslint

使用 eslint 来检查代码规范

bash 复制代码
npm i eslint eslint-webpack-plugin -D

webpack.config.ts 中添加 eslint 插件

ts 复制代码
import ESLintPlugin from 'eslint-webpack-plugin'

const config: Configuration = {
  plugins: [
    new ESLintPlugin({
      extensions: ['js', 'ts', 'vue'],
    }),
  ],
}

使用命令创建 eslintrc.mjs 文件

bash 复制代码
npm init @eslint/config@latest
js 复制代码
import { defineConfig } from 'eslint/config'
import globals from 'globals'
import js from '@eslint/js'
import tseslint from 'typescript-eslint'
import pluginVue from 'eslint-plugin-vue'

export default defineConfig([
  { files: ['**/*.{js,mjs,cjs,ts,vue}'] },
  {
    files: ['**/*.{js,mjs,cjs,ts,vue}'],
    languageOptions: { globals: globals.browser },
  },
  {
    files: ['**/*.{js,mjs,cjs,ts,vue}'],
    plugins: { js },
    extends: ['js/recommended'],
  },
  tseslint.configs.recommended,
  pluginVue.configs['flat/recommended'],
  {
    files: ['**/*.vue'],
    languageOptions: { parserOptions: { parser: tseslint.parser } },
  },
  {
    rules: {
      semi: ['error', 'never'],
      quotes: ['error', 'single'],
    },
  },
])

配置 lint 命令

json 复制代码
{
  "scripts": {
    "lint": "eslint --ext .js,.ts,.vue src"
  }
}

prettier

使用 prettier 来格式化代码

bash 复制代码
npm i prettier eslint-config-prettier eslint-plugin-prettier -D

eslintrc.mjs 中添加 prettier 插件

js 复制代码
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
export default defineConfig([
  // 放到最后
  eslintPluginPrettierRecommended,
])

创建 .prettierrc 文件

json 复制代码
{
  "semi": false,
  "singleQuote": true
}

react

webpack 配置 react

bash 复制代码
npm i react react-dom -S

修改 webpack.config.ts 文件,支持 tsx

ts 复制代码
const config: Configuration = {
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: ['.ts', '.js', '.json', '.tsx'],
  },
}

修改 tsconfig.json 文件,支持 jsx

json 复制代码
{
  "compilerOptions": {
    "jsx": "react-jsx"
  }
}

创建一个 App.tsx 文件,来测试 react 文件的加载

tsx 复制代码
import { useState } from 'react'

export default function App() {
  const [count, setCount] = useState(0)
  return (
    <>
      <div>count:{count}</div>
      <button onClick={() => setCount(count + 1)}>+</button>
    </>
  )
}

修改 main.ts 文件

ts 复制代码
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'

const root = ReactDOM.createRoot(document.getElementById('app') as HTMLElement)

root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)

运行就可以看到页面上显示了一个按钮,点击按钮会增加 count 的值

优化

生产环境下,我们需要对代码进行优化,比如压缩代码、提取公共代码、代码分割等。

打包清理之前的文件

ts 复制代码
const config: Configuration = {
  output: {
    ...
    clean: true,
  },
}

css

css 提取

bash 复制代码
npm i mini-css-extract-plugin -D
ts 复制代码
import MiniCssExtractPlugin from 'mini-css-extract-plugin'
const config: Configuration = {
  modules: {
    rules: [
      {
        test: /\.css$/,
        use: [
          isDev ? 'vue-style-loader' : MiniCssExtractPlugin.loader,
          'css-loader',
        ],
      },
      {
        test: /\.scss$/,
        use: [
          isDev ? 'vue-style-loader' : MiniCssExtractPlugin.loader,
          'css-loader',
          'sass-loader',
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css',
    }),
  ],
}

打包运行后,发现 css 文件已经被提取到单独的文件中了,但是部分代码没有压缩,下面我们来配置压缩插件。

css 压缩

bash 复制代码
npm i css-minimizer-webpack-plugin -D
ts 复制代码
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'

代码压缩

webpack 生产环境下,会自动压缩代码,但是我们可以使用 terser-webpack-plugin 来自定义压缩配置

bash 复制代码
npm i terser-webpack-plugin -D
ts 复制代码
import TerserPlugin from 'terser-webpack-plugin'

const config: Configuration = {
  optimization: {
    minimizer: [
      new TerserPlugin({
        extractComments: false,
        parallel: true,
        terserOptions: {
          compress: {
            drop_console: true,
          },
          format: {
            comments: false,
          },
        },
      }),
    ],
  },
}
相关推荐
DT——13 分钟前
Vue2和Vue3的区别
开发语言·javascript·vue.js
Python私教1 小时前
Vue 在现代 Web 开发中的优势
前端·javascript·vue.js
fridayCodeFly1 小时前
<KeepAlive>和<keep-alive>有什么区别
前端·javascript·vue.js
寻梦人121381 小时前
Vite管理的Vue3项目中monaco editer的使用以及组件封装
前端·javascript·vue.js·vscode
瓶子丶1 小时前
企业微信通讯录效果?拿捏!!!
vue.js·uni-app
洋茄子炒鸡蛋2 小时前
有没有一刻,突然平静地想离职(VUE中使用XLSX插件导入Excel文件,日期解析存在误差)
vue.js·excel
outsider_友人A2 小时前
手摸手带你封装Vue组件库(16)Carousel走马灯组件
前端·javascript·vue.js
程序员小刚2 小时前
基于SpringBoot + Vue 的汽车租赁管理系统
vue.js·spring boot·汽车
bigyoung2 小时前
过滤tree数据中某些数据
前端·javascript·vue.js
计算机-秋大田2 小时前
基于Spring Boot的消防物资存储系统的设计与实现(LW+源码+讲解)
java·vue.js·spring boot·后端·课程设计