想要从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.https://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压缩插件,将生产环境的代码进行压缩。
就到这里,后续再不断完善。欢迎大佬指正,提出打包优化意见,谢谢观赏