Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Dec 11, 2019
1 parent 121b2c5 commit 5108e1f
Show file tree
Hide file tree
Showing 12 changed files with 734 additions and 4,857 deletions.
5 changes: 1 addition & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
*.log
.DS_Store
node_modules
.rts2_cache_cjs
.rts2_cache_esm
.rts2_cache_umd
.rts2_cache_system
dist
link.sh
3 changes: 3 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
semi: false
singleQuote: true
printWidth: 80
24 changes: 6 additions & 18 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,24 @@
"license": "MIT",
"author": "Evan You",
"main": "dist/index.js",
"module": "dist/vue-loader.esm.js",
"typings": "dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"start": "tsdx watch",
"build": "tsdx build",
"test": "tsdx test",
"lint": "tsdx lint"
"build": "tsc"
},
"peerDependencies": {
"@vue/compiler-sfc": "^3.0.0-alpha.0"
},
"husky": {
"hooks": {
"pre-commit": "tsdx lint --fix"
}
},
"prettier": {
"printWidth": 80,
"semi": false,
"singleQuote": true
"dependencies": {
"hash-sum": "^2.0.0",
"loader-utils": "^1.2.3"
},
"devDependencies": {
"@types/jest": "^24.0.23",
"@types/hash-sum": "^1.0.0",
"@types/loader-utils": "^1.1.3",
"@types/webpack": "^4.41.0",
"husky": "^3.1.0",
"tsdx": "^0.11.0",
"tslib": "^1.10.0",
"typescript": "^3.7.3",
"webpack": "^4.41.2"
}
Expand Down
180 changes: 176 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,191 @@
import * as webpack from 'webpack'
import { TemplateCompiler, CompilerOptions } from '@vue/compiler-sfc'
import path from 'path'
import qs from 'querystring'
import hash from 'hash-sum'
import loaderUtils from 'loader-utils'
import {
parse,
TemplateCompiler,
CompilerOptions,
SFCBlock,
TemplateCompileOptions
} from '@vue/compiler-sfc'
import { selectBlock } from './select'

const VueLoaderPlugin = require('./plugin')

export interface VueLoaderOptions {
transformAssetUrls?: { [tag: string]: string | Array<string> }
transformAssetUrls?: TemplateCompileOptions['transformAssetUrls']
compiler?: TemplateCompiler
compilerOptions?: CompilerOptions
hotReload?: boolean
productionMode?: boolean
cacheDirectory?: string
cacheIdentifier?: string
exposeFilename?: boolean
appendExtension?: boolean
}

const vueLoader: webpack.loader.Loader = function (source) {
let errorEmitted = false

const loader: webpack.loader.Loader = function(source) {
const loaderContext = this

// check if plugin is installed
if (
!errorEmitted &&
!(loaderContext as any)['thread-loader'] &&
!(loaderContext as any)[VueLoaderPlugin.NS]
) {
loaderContext.emitError(
new Error(
`vue-loader was used without the corresponding plugin. ` +
`Make sure to include VueLoaderPlugin in your webpack config.`
)
)
errorEmitted = true
}

const stringifyRequest = (r: string) =>
loaderUtils.stringifyRequest(loaderContext, r)

const {
target,
minimize,
sourceMap,
rootContext,
resourcePath,
resourceQuery
} = loaderContext

const rawQuery = resourceQuery.slice(1)
const inheritQuery = `&${rawQuery}`
const incomingQuery = qs.parse(rawQuery)
const options = (loaderUtils.getOptions(loaderContext) ||
{}) as VueLoaderOptions

const isServer = target === 'node'
const isProduction =
options.productionMode || minimize || process.env.NODE_ENV === 'production'

const filename = path.basename(resourcePath)
const context = rootContext || process.cwd()
const sourceRoot = path.dirname(path.relative(context, resourcePath))

const descriptor = parse(String(source), {
filename,
sourceMap,
sourceRoot
})

// if the query has a type field, this is a language block request
// e.g. foo.vue?type=template&id=xxxxx
// and we will return early
if (incomingQuery.type) {
return selectBlock(
descriptor,
loaderContext,
incomingQuery,
!!options.appendExtension
)
}

// module id for scoped CSS & hot-reload
const rawShortFilePath = path
.relative(context, resourcePath)
.replace(/^(\.\.[\/\\])+/, '')
const shortFilePath = rawShortFilePath.replace(/\\/g, '/') + resourceQuery
const id = hash(isProduction ? shortFilePath + '\n' + source : shortFilePath)

// feature information
const hasScoped = descriptor.styles.some(s => s.scoped)
const needsHotReload =
!isServer &&
!isProduction &&
(descriptor.script || descriptor.template) &&
options.hotReload !== false

// template
let templateImport = `const render = () => {}`
let templateRequest
if (descriptor.template) {
const src = descriptor.template.src || resourcePath
const idQuery = `&id=${id}`
const scopedQuery = hasScoped ? `&scoped=true` : ``
const attrsQuery = attrsToQuery(descriptor.template.attrs)
const query = `?vue&type=template${idQuery}${scopedQuery}${attrsQuery}${inheritQuery}`
const request = (templateRequest = stringifyRequest(src + query))
templateImport = `import render from ${request}`
}

// script
let scriptImport = `const script = {}`
if (descriptor.script) {
const src = descriptor.script.src || resourcePath
const attrsQuery = attrsToQuery(descriptor.script.attrs, 'js')
const query = `?vue&type=script${attrsQuery}${inheritQuery}`
const request = stringifyRequest(src + query)
scriptImport =
`import script from ${request}\n` + `export * from ${request}` // support named exports
}

// styles
let stylesCode = ``
if (descriptor.styles.length) {
// TODO handle style
}

let code = [
templateImport,
scriptImport,
stylesCode,
`script.render = render`
].join('\n')

if (descriptor.customBlocks && descriptor.customBlocks.length) {
// TODO custom blocks
}

if (needsHotReload) {
// TODO hot reload
templateRequest
}

// Expose filename. This is used by the devtools and Vue runtime warnings.
if (!isProduction) {
// Expose the file's full path in development, so that it can be opened
// from the devtools.
code += `\nscript.__file = ${JSON.stringify(
rawShortFilePath.replace(/\\/g, '/')
)}`
} else if (options.exposeFilename) {
// Libraies can opt-in to expose their components' filenames in production builds.
// For security reasons, only expose the file's basename in production.
code += `\nscript.__file = ${JSON.stringify(filename)}`
}

// finalize
code += `\n\nexport default script`
return code
}

// these are built-in query parameters so should be ignored
// if the user happen to add them as attrs
const ignoreList = ['id', 'index', 'src', 'type']

function attrsToQuery(attrs: SFCBlock['attrs'], langFallback?: string): string {
let query = ``
for (const name in attrs) {
const value = attrs[name]
if (!ignoreList.includes(name)) {
query += `&${qs.escape(name)}=${value ? qs.escape(String(value)) : ``}`
}
}
if (langFallback && !(`lang` in attrs)) {
query += `&lang=${langFallback}`
}
return query
}

export default vueLoader
;(loader as any).VueLoaderPlugin = VueLoaderPlugin
module.exports = loader
Loading

0 comments on commit 5108e1f

Please sign in to comment.