Skip to content

Commit 5108e1f

Browse files
committed
wip
1 parent 121b2c5 commit 5108e1f

File tree

12 files changed

+734
-4857
lines changed

12 files changed

+734
-4857
lines changed

.gitignore

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
*.log
22
.DS_Store
33
node_modules
4-
.rts2_cache_cjs
5-
.rts2_cache_esm
6-
.rts2_cache_umd
7-
.rts2_cache_system
84
dist
5+
link.sh

.prettierrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
semi: false
2+
singleQuote: true
3+
printWidth: 80

package.json

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,24 @@
44
"license": "MIT",
55
"author": "Evan You",
66
"main": "dist/index.js",
7-
"module": "dist/vue-loader.esm.js",
87
"typings": "dist/index.d.ts",
98
"files": [
109
"dist"
1110
],
1211
"scripts": {
13-
"start": "tsdx watch",
14-
"build": "tsdx build",
15-
"test": "tsdx test",
16-
"lint": "tsdx lint"
12+
"build": "tsc"
1713
},
1814
"peerDependencies": {
1915
"@vue/compiler-sfc": "^3.0.0-alpha.0"
2016
},
21-
"husky": {
22-
"hooks": {
23-
"pre-commit": "tsdx lint --fix"
24-
}
25-
},
26-
"prettier": {
27-
"printWidth": 80,
28-
"semi": false,
29-
"singleQuote": true
17+
"dependencies": {
18+
"hash-sum": "^2.0.0",
19+
"loader-utils": "^1.2.3"
3020
},
3121
"devDependencies": {
32-
"@types/jest": "^24.0.23",
22+
"@types/hash-sum": "^1.0.0",
23+
"@types/loader-utils": "^1.1.3",
3324
"@types/webpack": "^4.41.0",
34-
"husky": "^3.1.0",
35-
"tsdx": "^0.11.0",
36-
"tslib": "^1.10.0",
3725
"typescript": "^3.7.3",
3826
"webpack": "^4.41.2"
3927
}

src/index.ts

Lines changed: 176 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,191 @@
11
import * as webpack from 'webpack'
2-
import { TemplateCompiler, CompilerOptions } from '@vue/compiler-sfc'
2+
import path from 'path'
3+
import qs from 'querystring'
4+
import hash from 'hash-sum'
5+
import loaderUtils from 'loader-utils'
6+
import {
7+
parse,
8+
TemplateCompiler,
9+
CompilerOptions,
10+
SFCBlock,
11+
TemplateCompileOptions
12+
} from '@vue/compiler-sfc'
13+
import { selectBlock } from './select'
14+
15+
const VueLoaderPlugin = require('./plugin')
316

417
export interface VueLoaderOptions {
5-
transformAssetUrls?: { [tag: string]: string | Array<string> }
18+
transformAssetUrls?: TemplateCompileOptions['transformAssetUrls']
619
compiler?: TemplateCompiler
720
compilerOptions?: CompilerOptions
821
hotReload?: boolean
922
productionMode?: boolean
1023
cacheDirectory?: string
1124
cacheIdentifier?: string
1225
exposeFilename?: boolean
26+
appendExtension?: boolean
1327
}
1428

15-
const vueLoader: webpack.loader.Loader = function (source) {
29+
let errorEmitted = false
30+
31+
const loader: webpack.loader.Loader = function(source) {
1632
const loaderContext = this
33+
34+
// check if plugin is installed
35+
if (
36+
!errorEmitted &&
37+
!(loaderContext as any)['thread-loader'] &&
38+
!(loaderContext as any)[VueLoaderPlugin.NS]
39+
) {
40+
loaderContext.emitError(
41+
new Error(
42+
`vue-loader was used without the corresponding plugin. ` +
43+
`Make sure to include VueLoaderPlugin in your webpack config.`
44+
)
45+
)
46+
errorEmitted = true
47+
}
48+
49+
const stringifyRequest = (r: string) =>
50+
loaderUtils.stringifyRequest(loaderContext, r)
51+
52+
const {
53+
target,
54+
minimize,
55+
sourceMap,
56+
rootContext,
57+
resourcePath,
58+
resourceQuery
59+
} = loaderContext
60+
61+
const rawQuery = resourceQuery.slice(1)
62+
const inheritQuery = `&${rawQuery}`
63+
const incomingQuery = qs.parse(rawQuery)
64+
const options = (loaderUtils.getOptions(loaderContext) ||
65+
{}) as VueLoaderOptions
66+
67+
const isServer = target === 'node'
68+
const isProduction =
69+
options.productionMode || minimize || process.env.NODE_ENV === 'production'
70+
71+
const filename = path.basename(resourcePath)
72+
const context = rootContext || process.cwd()
73+
const sourceRoot = path.dirname(path.relative(context, resourcePath))
74+
75+
const descriptor = parse(String(source), {
76+
filename,
77+
sourceMap,
78+
sourceRoot
79+
})
80+
81+
// if the query has a type field, this is a language block request
82+
// e.g. foo.vue?type=template&id=xxxxx
83+
// and we will return early
84+
if (incomingQuery.type) {
85+
return selectBlock(
86+
descriptor,
87+
loaderContext,
88+
incomingQuery,
89+
!!options.appendExtension
90+
)
91+
}
92+
93+
// module id for scoped CSS & hot-reload
94+
const rawShortFilePath = path
95+
.relative(context, resourcePath)
96+
.replace(/^(\.\.[\/\\])+/, '')
97+
const shortFilePath = rawShortFilePath.replace(/\\/g, '/') + resourceQuery
98+
const id = hash(isProduction ? shortFilePath + '\n' + source : shortFilePath)
99+
100+
// feature information
101+
const hasScoped = descriptor.styles.some(s => s.scoped)
102+
const needsHotReload =
103+
!isServer &&
104+
!isProduction &&
105+
(descriptor.script || descriptor.template) &&
106+
options.hotReload !== false
107+
108+
// template
109+
let templateImport = `const render = () => {}`
110+
let templateRequest
111+
if (descriptor.template) {
112+
const src = descriptor.template.src || resourcePath
113+
const idQuery = `&id=${id}`
114+
const scopedQuery = hasScoped ? `&scoped=true` : ``
115+
const attrsQuery = attrsToQuery(descriptor.template.attrs)
116+
const query = `?vue&type=template${idQuery}${scopedQuery}${attrsQuery}${inheritQuery}`
117+
const request = (templateRequest = stringifyRequest(src + query))
118+
templateImport = `import render from ${request}`
119+
}
120+
121+
// script
122+
let scriptImport = `const script = {}`
123+
if (descriptor.script) {
124+
const src = descriptor.script.src || resourcePath
125+
const attrsQuery = attrsToQuery(descriptor.script.attrs, 'js')
126+
const query = `?vue&type=script${attrsQuery}${inheritQuery}`
127+
const request = stringifyRequest(src + query)
128+
scriptImport =
129+
`import script from ${request}\n` + `export * from ${request}` // support named exports
130+
}
131+
132+
// styles
133+
let stylesCode = ``
134+
if (descriptor.styles.length) {
135+
// TODO handle style
136+
}
137+
138+
let code = [
139+
templateImport,
140+
scriptImport,
141+
stylesCode,
142+
`script.render = render`
143+
].join('\n')
144+
145+
if (descriptor.customBlocks && descriptor.customBlocks.length) {
146+
// TODO custom blocks
147+
}
148+
149+
if (needsHotReload) {
150+
// TODO hot reload
151+
templateRequest
152+
}
153+
154+
// Expose filename. This is used by the devtools and Vue runtime warnings.
155+
if (!isProduction) {
156+
// Expose the file's full path in development, so that it can be opened
157+
// from the devtools.
158+
code += `\nscript.__file = ${JSON.stringify(
159+
rawShortFilePath.replace(/\\/g, '/')
160+
)}`
161+
} else if (options.exposeFilename) {
162+
// Libraies can opt-in to expose their components' filenames in production builds.
163+
// For security reasons, only expose the file's basename in production.
164+
code += `\nscript.__file = ${JSON.stringify(filename)}`
165+
}
166+
167+
// finalize
168+
code += `\n\nexport default script`
169+
return code
170+
}
171+
172+
// these are built-in query parameters so should be ignored
173+
// if the user happen to add them as attrs
174+
const ignoreList = ['id', 'index', 'src', 'type']
175+
176+
function attrsToQuery(attrs: SFCBlock['attrs'], langFallback?: string): string {
177+
let query = ``
178+
for (const name in attrs) {
179+
const value = attrs[name]
180+
if (!ignoreList.includes(name)) {
181+
query += `&${qs.escape(name)}=${value ? qs.escape(String(value)) : ``}`
182+
}
183+
}
184+
if (langFallback && !(`lang` in attrs)) {
185+
query += `&lang=${langFallback}`
186+
}
187+
return query
17188
}
18189

19-
export default vueLoader
190+
;(loader as any).VueLoaderPlugin = VueLoaderPlugin
191+
module.exports = loader

0 commit comments

Comments
 (0)