Skip to content

Commit efe3a88

Browse files
committed
feat: webpack loader
1 parent a51ff74 commit efe3a88

26 files changed

+560
-1
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,12 @@
3030

3131
<tr>
3232
<td><a href="https://juejin.cn/post/7265516154847363106">Service Worker的应用</a></td>
33+
<td><a href="./packages/service-worker">packages/service-worker</a></td>
3334
</tr>
3435

3536
<tr>
3637
<td><a href="https://juejin.cn/post/7265516484028383266">初探webpack之编写loader</a></td>
38+
<td><a href="./packages/webpack-loader">packages/webpack-loader</a></td>
3739
</tr>
3840

3941
<tr>

packages/webpack-loader/.editorconfig

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
indent_style = space
6+
indent_size = 4
7+
end_of_line = lf
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true

packages/webpack-loader/.eslintrc.js

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
module.exports = {
2+
parser: "vue-eslint-parser",
3+
extends: ["eslint:recommended", "plugin:prettier/recommended"],
4+
overrides: [
5+
{
6+
files: ["*.ts"],
7+
parser: "@typescript-eslint/parser",
8+
plugins: ["@typescript-eslint"],
9+
extends: ["plugin:@typescript-eslint/recommended"],
10+
},
11+
{
12+
files: ["*.vue"],
13+
parser: "vue-eslint-parser",
14+
extends: [
15+
"plugin:vue/recommended",
16+
"plugin:prettier/recommended",
17+
"plugin:@typescript-eslint/recommended",
18+
],
19+
},
20+
],
21+
parserOptions: {
22+
ecmaVersion: 2020,
23+
sourceType: "module",
24+
parser: "@typescript-eslint/parser",
25+
},
26+
env: {
27+
browser: true,
28+
node: true,
29+
commonjs: true,
30+
es2021: true,
31+
},
32+
rules: {
33+
// 分号
34+
"semi": "error",
35+
// 对象键值引号样式保持一致
36+
"quote-props": ["error", "consistent-as-needed"],
37+
// 箭头函数允许单参数不带括号
38+
"arrow-parens": ["error", "as-needed"],
39+
// no var
40+
"no-var": "error",
41+
// const
42+
"prefer-const": "error",
43+
// 允许console
44+
"no-console": "off",
45+
},
46+
};
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module.exports = {
2+
presets: [
3+
[
4+
"@babel/preset-env",
5+
{
6+
useBuiltIns: "usage",
7+
corejs: 3,
8+
modules: false,
9+
},
10+
],
11+
],
12+
};

packages/webpack-loader/package.json

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"name": "webpack-loader",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"license": "MIT",
6+
"scripts": {
7+
"build": "cross-env NODE_ENV=production webpack --config webpack.config.js",
8+
"dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.js"
9+
},
10+
"devDependencies": {
11+
"@babel/core": "7.15.8",
12+
"@babel/plugin-syntax-typescript": "7.14.5",
13+
"@babel/preset-env": "7.15.8",
14+
"@webpack-cli/serve": "2.0.5",
15+
"babel-loader": "8.2.2",
16+
"cross-env": "7.0.3",
17+
"css-loader": "6.4.0",
18+
"eslint-plugin-vue": "7.19.1",
19+
"file-loader": "6.2.0",
20+
"html-webpack-plugin": "5.3.2",
21+
"husky": "7.0.2",
22+
"lint-staged": "11.2.3",
23+
"postcss": "8.3.9",
24+
"postcss-loader": "6.2.0",
25+
"prettier": "2.4.1",
26+
"sass": "1.43.2",
27+
"sass-loader": "10.1.1",
28+
"ts-loader": "9.2.6",
29+
"tslib": "2.6.2",
30+
"url-loader": "4.1.1",
31+
"vue-class-component": "7.2.6",
32+
"vue-eslint-parser": "7.11.0",
33+
"vue-loader": "15.9.8",
34+
"vue-property-decorator": "9.1.2",
35+
"vue-style-loader": "4.1.3",
36+
"vue-template-compiler": "2.6.14",
37+
"vuex-class": "0.3.2",
38+
"webpack": "5.56.1",
39+
"webpack-cli": "4.10.0",
40+
"webpack-dev-server": "4.3.1"
41+
},
42+
"dependencies": {
43+
"core-js": "3",
44+
"vue": "2.6.14",
45+
"vue-router": "3.5.2",
46+
"vuex": "3.6.2"
47+
},
48+
"lint-staged": {
49+
"*.{js,vue,ts}": [
50+
"eslint --fix"
51+
]
52+
}
53+
}
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
6+
<meta name="viewport" content="width=device-width,initial-scale=1.0">
7+
<title><%= htmlWebpackPlugin.options.title %></title>
8+
</head>
9+
<body>
10+
<div id="app"></div>
11+
<!-- built files will be auto injected -->
12+
</body>
13+
</html>

packages/webpack-loader/src/App.vue

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<template>
2+
<div>
3+
<router-view />
4+
</div>
5+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
$color-blue: #4C98F7;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<template>
2+
<div>Example A</div>
3+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { Component, Vue } from "vue-property-decorator";
2+
@Component
3+
export default class TabA extends Vue {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<template>
2+
<div>Example B</div>
3+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { Component, Vue } from "vue-property-decorator";
2+
@Component
3+
export default class TabB extends Vue {}

packages/webpack-loader/src/index.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import "./main";
2+
3+
import { add } from "./sum";
4+
5+
console.log(add(1, 1));

packages/webpack-loader/src/main.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import Vue from "vue";
2+
3+
import App from "./App.vue";
4+
import Router from "./router";
5+
import Store from "./store";
6+
7+
const app = new Vue({
8+
router: Router,
9+
store: Store,
10+
...App,
11+
});
12+
app.$mount("#app");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import Vue from "vue";
2+
import VueRouter from "vue-router";
3+
4+
Vue.use(VueRouter);
5+
6+
import TabA from "../components/tab-a/tab-a.vue";
7+
import TabB from "../components/tab-b/tab-b.vue";
8+
import FrameWork from "../views/framework/framework.vue";
9+
10+
const routes = [
11+
{
12+
path: "/",
13+
component: FrameWork,
14+
children: [
15+
{
16+
path: "tab-a",
17+
name: "TabA",
18+
component: TabA,
19+
},
20+
{
21+
path: "tab-b",
22+
name: "TabB",
23+
component: TabB,
24+
},
25+
],
26+
},
27+
];
28+
29+
export default new VueRouter({
30+
routes,
31+
});

packages/webpack-loader/src/sfc.d.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
declare module "*.vue" {
2+
import Vue from "vue";
3+
export default Vue;
4+
}
35.8 KB
Loading
5.01 KB
Loading
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import Vue from "vue";
2+
import Vuex from "vuex";
3+
4+
Vue.use(Vuex);
5+
6+
export interface State {
7+
text: string;
8+
}
9+
const state: State = {
10+
text: "Value",
11+
};
12+
13+
const getters = {
14+
getText(state: State) {
15+
return state.text;
16+
},
17+
};
18+
19+
const mutations = {
20+
setText: (state: State, text: string) => {
21+
state.text = text;
22+
},
23+
};
24+
25+
export default new Vuex.Store({
26+
state,
27+
mutations,
28+
getters,
29+
});

packages/webpack-loader/src/sum.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const add = (a: number, b: number): number => a + b;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<template>
2+
<div>
3+
<section>
4+
<img src="../../static/vue.jpg" alt="" class="vue" />
5+
<img src="../../static/vue-large.png" alt="" class="vue-large" />
6+
<div class="example">{{ msg }}</div>
7+
<button @click="toast">Alert</button>
8+
</section>
9+
<section>
10+
<router-link to="/tab-a">TabA</router-link>
11+
<router-link to="/tab-b">TabB</router-link>
12+
<router-view />
13+
</section>
14+
<section>
15+
<button @click="setVuexValue">Set Vuex Value</button>
16+
<div>{{ text }}</div>
17+
</section>
18+
</div>
19+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// scoped
2+
@import '../../common/styles.scss';
3+
4+
.vue {
5+
width: 100px;
6+
}
7+
8+
.vue-large {
9+
width: 300px;
10+
}
11+
12+
.example {
13+
color: $color-blue;
14+
font-size: 30px;
15+
}
16+
17+
section {
18+
margin: 10px;
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Component, Vue } from "vue-property-decorator";
2+
import { State } from "vuex-class";
3+
4+
@Component
5+
export default class FrameWork extends Vue {
6+
protected msg = "Example";
7+
8+
@State("text") text!: string;
9+
10+
protected toast() {
11+
window?.alert("ExampleMessage");
12+
}
13+
14+
protected setVuexValue() {
15+
this.$store.commit("setText", "New Value");
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
const { promisify } = require("util");
4+
const loaderUtils = require("loader-utils");
5+
6+
const readDir = promisify(fs.readdir);
7+
const readFile = promisify(fs.readFile);
8+
9+
module.exports = async function (source) {
10+
const done = this.async();
11+
const filePath = this.context;
12+
const fileName = this.resourcePath.replace(filePath + "/", "");
13+
14+
const options = loaderUtils.getOptions(this) || {};
15+
const styleRegExp = new RegExp(options.style.map(it => `${fileName}\\.${it}$`).join("|"));
16+
const scriptRegExp = new RegExp(options.script.map(it => `${fileName}\\.${it}$`).join("|"));
17+
18+
let stylePath = null;
19+
let scriptPath = null;
20+
21+
const files = await readDir(filePath);
22+
files.forEach(file => {
23+
if (styleRegExp.test(file)) stylePath = path.join(filePath, file);
24+
if (scriptRegExp.test(file)) scriptPath = path.join(filePath, file);
25+
});
26+
27+
// 存在匹配节点且原`.vue`文件不存在`script`标签
28+
if (scriptPath && !/<script[\s\S]*?>/.test(source)) {
29+
const extName = scriptPath.split(".").pop();
30+
if (extName) {
31+
const content = await readFile(scriptPath, "utf8");
32+
const scriptTagContent = [
33+
"<script ",
34+
extName === "js" ? "" : `lang="${extName}" `,
35+
">\n",
36+
content,
37+
"</script>",
38+
].join("");
39+
source = source + "\n" + scriptTagContent;
40+
}
41+
}
42+
43+
// 存在匹配节点且原`.vue`文件不存在`style`标签
44+
if (stylePath && !/<style[\s\S]*?>/.test(source)) {
45+
const extName = stylePath.split(".").pop();
46+
if (extName) {
47+
const content = await readFile(stylePath, "utf8");
48+
const scoped = /\/\/[\s]scoped[\n]/.test(content) ? true : false;
49+
const styleTagContent = [
50+
"<style ",
51+
extName === "css" ? "" : `lang="${extName}" `,
52+
scoped ? "scoped " : " ",
53+
">\n",
54+
content,
55+
"</style>",
56+
].join("");
57+
source = source + "\n" + styleTagContent;
58+
}
59+
}
60+
61+
// console.log(stylePath, scriptPath, source);
62+
done(null, source);
63+
};

0 commit comments

Comments
 (0)