Commit d04f9cf1 authored by Evan You's avatar Evan You

feat: support configuring loader for custom blocks via resourceQuery

parent 600ca0cb
...@@ -5,8 +5,8 @@ const stylePostLoaderPath = require.resolve('./style-post-loader') ...@@ -5,8 +5,8 @@ const stylePostLoaderPath = require.resolve('./style-post-loader')
module.exports = code => code module.exports = code => code
// This pitching loader is responsible for catching all src import requests // This pitching loader is responsible for intercepting all vue block requests
// from within vue files and transform it into appropriate requests // and transform it into appropriate requests.
module.exports.pitch = function (remainingRequest) { module.exports.pitch = function (remainingRequest) {
const query = qs.parse(this.resourceQuery.slice(1)) const query = qs.parse(this.resourceQuery.slice(1))
...@@ -14,32 +14,51 @@ module.exports.pitch = function (remainingRequest) { ...@@ -14,32 +14,51 @@ module.exports.pitch = function (remainingRequest) {
return return
} }
const loaders = this.loaders.slice(1) // remove self
// loader.request contains both the resolved loader path and its options
// query (e.g. ??ref-0)
const toLoaderString = loader => loader.request
const genRequest = loaderStrings => {
// important: dedupe
loaderStrings = Array.from(new Set(loaderStrings))
return loaderUtils.stringifyRequest(this, '-!' + [
...loaderStrings,
this.resourcePath + this.resourceQuery
].join('!'))
}
// Inject style-post-loader before css-loader for scoped CSS and trimming // Inject style-post-loader before css-loader for scoped CSS and trimming
if (query.type === `style`) { if (query.type === `style`) {
const cssLoaderIndex = this.loaders.findIndex(l => /\/css-loader/.test(l.request)) const cssLoaderIndex = loaders.findIndex(l => /\/css-loader/.test(l.request))
if (cssLoaderIndex) { if (cssLoaderIndex) {
const afterLoaders = this.loaders.slice(1, cssLoaderIndex + 1).map(l => l.request) const afterLoaders = loaders.slice(0, cssLoaderIndex + 1).map(toLoaderString)
const beforeLoaders = this.loaders.slice(cssLoaderIndex + 1).map(l => l.request) const beforeLoaders = loaders.slice(cssLoaderIndex + 1).map(toLoaderString)
const request = '-!' + [ const request = genRequest([
...afterLoaders, ...afterLoaders,
stylePostLoaderPath, stylePostLoaderPath,
...beforeLoaders, ...beforeLoaders
this.resourcePath + this.resourceQuery ])
].join('!')
// use cjs to ensure exports from (vue-)style-loader/css-loader are intact // use cjs to ensure exports from (vue-)style-loader/css-loader are intact
return `module.exports = require(${loaderUtils.stringifyRequest(this, request)})` return `module.exports = require(${request})`
} }
} }
// for templates: inject the template compiler // for templates: inject the template compiler
if (query.type === `template`) { if (query.type === `template`) {
const beforeLoaders = this.loaders.slice(1).map(l => l.request) const beforeLoaders = loaders.map(toLoaderString)
const request = '-!' + [ const request = genRequest([
templateLoaderPath + `??vue-loader-options`, templateLoaderPath + `??vue-loader-options`,
...beforeLoaders, ...beforeLoaders
this.resourcePath + this.resourceQuery ])
].join('!')
// the template compiler uses esm exports // the template compiler uses esm exports
return `export * from ${loaderUtils.stringifyRequest(this, request)}` return `export * from ${request}`
} }
// When the user defines a rule that has only resourceQuery but no test,
// both that rule and the cloned rule will match, resulting in duplicated
// loaders. Therefore it is necessary to perform a dedupe here.
const dedupedRequest = genRequest(loaders.map(toLoaderString))
return `module.exports = require(${dedupedRequest})`
} }
const qs = require('querystring') const qs = require('querystring')
const RuleSet = require('webpack/lib/RuleSet') const RuleSet = require('webpack/lib/RuleSet')
// TODO handling rules without `test` being matched twice
// e.g. a rule with just resourceQuery: /blockType=foo/
// TODO handle vueRule with oneOf // TODO handle vueRule with oneOf
module.exports = class VueLoaderPlugin { module.exports = class VueLoaderPlugin {
apply (compiler) { apply (compiler) {
// get a hold of the raw rules // get a hold of the raw rules
...@@ -114,10 +110,10 @@ function cloneRule (rule, normalizedRule, vueUse) { ...@@ -114,10 +110,10 @@ function cloneRule (rule, normalizedRule, vueUse) {
}, },
resourceQuery: query => { resourceQuery: query => {
const parsed = qs.parse(query.slice(1)) const parsed = qs.parse(query.slice(1))
if (parsed.lang == null) { const { resource, resourceQuery } = normalizedRule
if (resource && parsed.lang == null) {
return false return false
} }
const { resource, resourceQuery } = normalizedRule
if (resource && !resource(`${currentResource}.${parsed.lang}`)) { if (resource && !resource(`${currentResource}.${parsed.lang}`)) {
return false return false
} }
......
const { const {
bundle bundle,
mockBundleAndRun
} = require('./utils') } = require('./utils')
test('add custom blocks to the webpack output', done => { test('add custom blocks to the webpack output', done => {
...@@ -43,23 +44,22 @@ describe('example', function () { ...@@ -43,23 +44,22 @@ describe('example', function () {
}) })
}) })
// TODO test('passes Component to custom block loaders', done => {
// test('passes Component to custom block loaders', done => { mockBundleAndRun({
// mockBundleAndRun({ entry: 'custom-language.vue',
// entry: 'custom-language.vue', module: {
// module: { rules: [
// rules: [ {
// { resourceQuery: /blockType=documentation/,
// resourceQuery: /blockType=documentation/, loader: require.resolve('./mock-loaders/docs')
// loader: require.resolve('./mock-loaders/docs') }
// } ]
// ] }
// } }, ({ module }) => {
// }, ({ module }) => { expect(module.__docs).toContain('This is example documentation for a component.')
// expect(module.__docs).toContain('This is example documentation for a component.') done()
// done() })
// }) })
// })
test('custom blocks can be ignored', done => { test('custom blocks can be ignored', done => {
bundle({ bundle({
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment