使用webpack从0搭建vue3脚手架

想要从0开始搭建一个自己的项目脚手架,并不断优化,话不多说开搞。

首先初始化项目,个人习惯习惯用yarn

yarn init -y

node为我们初始化了package.json

安装webpack、webpack-cli、typescript(注意项目路径需要为英文)

yarn add webpack webpack-cli typescript -D

得到以下目录

接下来初始化typescript

npx tsc --init

npx 可以执行我们node_modules下面.bin目录下的可执行命令,初始完typescript我们就会得到一个tsconfig.json的配置文件,加下来编写我们的配置(根据自己喜欢进行配置)

{
  "compilerOptions": {
    "allowJs": true /* 是否允许编译js文件 */,
    "checkJs": true /* 是否检查js文件类型 */,
    "sourceMap": true /* 是否开启源代码映射 */,
    "allowUnreachableCode": false /* 是否允许无法访问的代码存在 */,
    "allowUnusedLabels": false /* 是否允许未使用的标签 */,
    "alwaysStrict": true /* 始终严格模式-确保在 ECMAScript 严格模式下解析文件,并为每个源文件发出“use strict” */,
    "exactOptionalPropertyTypes": false /* 是否允许可选类型('a'|'b')为undefined */,
    "noFallthroughCasesInSwitch": true /* switch语句是否必须有break */,
    "noImplicitAny": true /* 不能有隐式any */,
    "noImplicitOverride": true /* 当子类方法或属性覆盖父类时是否必须添加override修饰符 */,
    "noImplicitReturns": true /* 无隐式返回-启用后,TypeScript 将检查函数中的所有代码路径,以确保它们返回值 */,
    "noImplicitThis": true /* 没有隐含的this-对具有隐含“any”类型的“this”表达式引发错误 */,
    "noPropertyAccessFromIndexSignature": true /* 无法从索引签名访问属性-只能用xx['string']访问未定义属性 */,
    "noUncheckedIndexedAccess": true /* 没有未经检查的索引访问 -未知属性类型是否联合undefined */,
    "noUnusedLocals": true /* 报告未使用的局部变量的错误 */,
    "noUnusedParameters": false /* 报告未使用的局部变量的错误 */,
    "strict": true /* 严格模式 */,
    "strictBindCallApply": true /* 严格绑定调用应用-设置后,TypeScript 将检查函数的内置方法 ,并使用基础函数的正确参数调用:callbindapply */,
    "strictFunctionTypes": true /* 严格函数类型-启用后,此标志会导致更正确地检查函数参数 */,
    "strictNullChecks": true /* 严格的null检查 */,
    "strictPropertyInitialization": true /* 严格属性初始化 */,
    "useUnknownInCatchVariables": true /* 在 Catch 变量中使用未知变量 */,

    "target": "ESNext",
    "module": "ESNext",
    "jsx": "preserve",
    "rootDir": ".",
    "baseUrl": ".",
    "outDir": "./dist",
    "paths": {
      "@/*": ["src/*"]
    },
    "lib": ["ESNext", "DOM", "DOM.Iterable"],
    "importHelpers": true,
    "moduleResolution": "node",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*.ts", "src/**/*.vue", "src/**/*.d.ts"]
}

更多配置详见官网

TypeScript: JavaScript With Syntax For Types.TypeScript extends JavaScript by adding types to the language. TypeScript speeds up your development experience by catching errors and providing fixes before you even run your code.icon-default.png?t=N7T8https://www.typescriptlang.org/zh/加下来创建源代码目录以及公共目录,并添加模板和入口文件,以及配置目录,结构如下

接下来安装一些编译loader以及本地开发服务和html插件

yarn add ts-loader css-loader less-loader style-loader webpack-dev-server html-webpack-plugin -D

然后就可以开始我们打包配置文件的编写了,简单配置后如下

const path = require("path"); // node内部核心模块
const HtmlWebpackPlugin = require("html-webpack-plugin"); // html模板插件
const packageJson = require("../package.json"); // package.json
const { ProgressPlugin } = require("webpack"); // 编译进度显示

module.exports = {
  mode: "development", // 模式
  entry: "./src/main.ts", // 入口
  context: process.cwd(), // 项目根目录
  devtool: "source-map", // 开启源代码映射
  // 输出文件以及目录
  output: {
    filename: "js/[fullhash].js",
    path: path.resolve(__dirname, "../dist"),
    clean: true, // 每次打包清空打包目录
  },
  module: {
    rules: [
      // 处理图片
      {
        test: /\.(png|jpg|jpeg|gif)$/,
        type: "assets/resource",
      },
      // 处理css
      {
        test: /\.css/,
        use: ["style-laoder", "css-loader"],
      },
      // 处理less
      {
        test: /\.less$/,
        use: ["style-laoder", "css-loader", "less-loader"],
      },
      // 处理ts
      {
        test: /\.ts$/,
        loader: "ts-loader",
      },
    ],
  },
  // 本地服务配置
  devServer: {
    proxy: {},
    port: 8080,
    hot: true,
    open: false,
    historyApiFallback: true, // 本地刷新重定向到index.html
    client: {
      logging: "warn",
      overlay: false,
    },
    static: {
      watch: false,
    },
    watchFiles: ["src/**/*"],
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: packageJson.name, //  html的title用package.json的name
      template: path.resolve(__dirname, "../public/index.html"), // 模板路径
      showErrors: true, // 编译错误再页面最前面展示出来
      templateParameters: {
        // 模板中使用
        BASE_URL: "/",
      },
    }),
    new ProgressPlugin(),
  ],
};

再来看看我们的html模板的样子

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title><%= htmlWebpackPlugin.options.title %></title>
    <link
      rel="shortcut icon"
      href="<%= BASE_URL %>favicon.ico"
      type="image/x-icon"
    />
  </head>
  <body>
    <noscript
      >We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
      properly without JavaScript enabled. Please enable it to continue.
    </noscript>
    <div id="app"></div>
  </body>
</html>

<%= htmlWebpackPlugin.options.title %>会读取为我们配置中的title

<%= BASE_URL %>favicon.ico中BASE_URL会解析为我们配置的 "/" 所以这里遍历出来的网站ico路径为 "/favicon.ico"(我们需要把自己的ico文件放到public目录中)

noscript当浏览器没有开启javascript的时候展示,其中<%= htmlWebpackPlugin.options.title %>一样会取配置文件中的title

接下来安装vue需要的插件已经编译loader以及babel(将代码向前兼容)

yarn add @babel/core @babel/preset-env babel-loader vue-loader -D

更新配置文件,加入相关配置,将ts-loader配置处理.vue文件

const path = require("path"); // node内部核心模块
const HtmlWebpackPlugin = require("html-webpack-plugin"); // html模板插件
const { VueLoaderPlugin } = require("vue-loader"); // vue编译插件
const packageJson = require("../package.json"); // package.json
const { ProgressPlugin } = require("webpack"); // 编译进度显示

module.exports = {
  mode: "development", // 模式
  entry: "./src/main.ts", // 入口
  context: process.cwd(), // 项目根目录
  devtool: "source-map", // 开启源代码映射
  // 输出文件以及目录
  output: {
    filename: "js/[fullhash].js",
    path: path.resolve(__dirname, "../dist"),
    clean: true, // 每次打包清空打包目录
  },
  module: {
    rules: [
      // 处理图片
      {
        test: /\.(png|jpg|jpeg|gif)$/,
        type: "assets/resource",
      },
      // 处理css
      {
        test: /\.css/,
        use: ["style-laoder", "css-loader"],
      },
      // 处理less
      {
        test: /\.less$/,
        use: ["style-laoder", "css-loader", "less-loader"],
      },
      // babel处理兼容性
      {
        test: /\.m?js$/,
        loader: "babel-loader",
        exclude: /(node_modules|bower_components)/,
        options: {
          presets: ["@babel/preset-env"],
        },
      },
      // 处理vue
      {
        test: /\.vue$/,
        loader: "vue-loader",
      },
      // 处理ts
      {
        test: /\.ts$/,
        loader: "ts-loader",
        options: {
          configFile: path.resolve(process.cwd(), "tsconfig.json"),
          appendTsSuffixTo: [/\.vue$/],
        },
      },
    ],
  },
  // 本地服务配置
  devServer: {
    proxy: {},
    port: 8080,
    hot: true,
    open: false,
    historyApiFallback: true, // 本地刷新重定向到index.html
    client: {
      logging: "warn",
      overlay: false,
    },
    static: {
      watch: false,
    },
    watchFiles: ["src/**/*"],
  },
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "../src"), // 引用路径别名
    },
    extensions: [".vue", ".js", ".ts"], // 模块识别
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: packageJson.name, //  html的title用package.json的name
      template: path.resolve(__dirname, "../public/index.html"), // 模板路径
      showErrors: true, // 编译错误再页面最前面展示出来
      templateParameters: {
        // 模板中使用
        BASE_URL: "/",
      },
    }),
    new ProgressPlugin(),
    new VueLoaderPlugin(),
  ],
};

接下来安装vue,并使用vue创建一个基础的应用程序

yarn add vue

然后配置我们的运行命令,在package.json加入以下配置

"scripts": {
    "serve": "webpack serve --config config/webpack.config.js",
    "build": "webpack --config config/webpack.config.js"
  },

接着使用yarn serve启动看看,不出意外会报错吧。

因为我们ts没有声明vue模块,所以我们为vue添加模块声明

在src目录下新建env.d.ts,内容如下

declare module "*.vue" {
  import { DefineComponent } from "vue";
  const component: DefineComponent<{}, {}, any>;
  export default component;
}
declare module "*.css";

再次运行,成功!

接着我们给本地服务和打包构建分开

于是我们有了以下目录

然后修改启动命令

base.config.js如下

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { VueLoaderPlugin } = require("vue-loader");
const packageJson = require("../package.json");
const { ProgressPlugin } = require("webpack");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  mode: "development",
  entry: "./src/main.ts",
  context: process.cwd(),
  output: {
    filename: "js/[fullhash].js",
    path: path.resolve(__dirname, "../dist"),
    clean: true,
  },
  module: {
    rules: [
      {
        test: /\.(png|jpg|jpeg|gif)$/,
        type: "assets/resource",
      },
      {
        test: /\.vue$/,
        loader: "vue-loader",
      },
      {
        test: /\.css/,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
      {
        test: /\.less$/,
        use: ["less-loader"],
      },
      {
        test: /\.ts$/,
        loader: "ts-loader",
        options: {
          configFile: path.resolve(process.cwd(), "tsconfig.json"),
          appendTsSuffixTo: [/\.vue$/],
        },
      },
    ],
  },
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "../src"),
    },
    extensions: [".vue", ".js", ".ts"],
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: packageJson.name,
      template: path.resolve(__dirname, "../public/index.html"),
      showErrors: true,
      templateParameters: {
        BASE_URL: "/",
      },
    }),
    new MiniCssExtractPlugin({
      filename: "css/[fullhash].css",
    }),
    new VueLoaderPlugin(),
    new ProgressPlugin(),
  ],
  stats: "none",
};

这里我加上了拆分css为单独的文件,然后去掉了style-loader,把devServer、devtool抽到dev配置里面

接着我们看一下dev.config.js

const FriendlyErrorsWebpackPlugin = require("friendly-errors-webpack-plugin");
const baseConfig = require("./base.config");
const path = require("path");
module.exports = Object.assign(baseConfig, {
  mode: "development",
  devtool: "source-map",
  devServer: {
    proxy: {},
    port: 8080,
    hot: true,
    open: false,
    historyApiFallback: true,
    client: {
      logging: "warn",
      overlay: false,
    },
    static: {
      watch: false,
    },
    watchFiles: ["src/**/*"],
  },
  plugins: [
    ...baseConfig.plugins,
    new FriendlyErrorsWebpackPlugin({
      compilationSuccessInfo: {
        messages: ["您的项目已运行到 http://localhost:8080"],
      },
    }),
  ],
});

这里我加了一个友好的错误编译提示插件

最后来看看prod.config.js

const baseConfig = require("./base.config");
const copyPlugin = require("copy-webpack-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const path = require("path");
const TerserPlugin = require("terser-webpack-plugin");
module.exports = Object.assign(baseConfig, {
  mode: "production",
  module: {
    rules: [
      ...baseConfig.module.rules,
      {
        test: /\.m?js$/,
        loader: "babel-loader",
        exclude: /(node_modules|bower_components)/,
        options: {
          presets: ["@babel/preset-env"],
        },
      },
    ],
  },
  optimization: {
    minimize: true,
    minimizer: [new CssMinimizerPlugin(), new TerserPlugin()],
  },
  plugins: [
    ...baseConfig.plugins,
    new copyPlugin({
      patterns: [path.resolve(__dirname, "../public/favicon.ico")],
    }),
  ],
});

这里我们新加了copy-webpack-plugin,用于复制文件到打包目录,因为我在打包的时候发现favicon.ico并没有被打包进dist目录,所以使用该插件直接将favicon文件复制过去;

然后我们把babel-loader拿到了生产打包里面,开发应该不会用很低版本的浏览器吧,一般不会有太大的兼容性问题,因为babel转义代码会比较慢,本地开发个人觉得不需要加上;

然后有个optimization用于控制代码压缩,这里我们用到了css亚索茶碱和js压缩插件,将生产环境的代码进行压缩。

就到这里,后续再不断完善。欢迎大佬指正,提出打包优化意见,谢谢观赏

  • 23
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qw.Dong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值