1
|
1
|
import * as path from 'path'
|
2
|
1
|
import filter from 'lodash/filter'
|
3
|
1
|
import map from 'lodash/map'
|
4
|
1
|
import values from 'lodash/values'
|
5
|
1
|
import flatten from 'lodash/flatten'
|
6
|
1
|
import loaderUtils from 'loader-utils'
|
7
|
1
|
import { generate } from 'escodegen'
|
8
|
1
|
import toAst from 'to-ast'
|
9
|
1
|
import { builders as b } from 'ast-types'
|
10
|
1
|
import { compile } from 'vue-inbrowser-compiler'
|
11
|
|
import * as Rsg from 'react-styleguidist'
|
12
|
1
|
import chunkify from 'react-styleguidist/lib/loaders/utils/chunkify'
|
13
|
1
|
import getImports from 'react-styleguidist/lib/loaders/utils/getImports'
|
14
|
1
|
import requireIt from 'react-styleguidist/lib/loaders/utils/requireIt'
|
15
|
1
|
import resolveESModule from 'react-styleguidist/lib/loaders/utils/resolveESModule'
|
16
|
|
import { StyleguidistContext } from '../types/StyleGuide'
|
17
|
1
|
import expandDefaultComponent from './utils/expandDefaultComponent'
|
18
|
1
|
import getComponentVueDoc from './utils/getComponentVueDoc'
|
19
|
1
|
import importCodeExampleFile from './utils/importCodeExampleFile'
|
20
|
1
|
import absolutize from './utils/absolutize'
|
21
|
1
|
import getScript from './utils/getScript'
|
22
|
1
|
import getParser from './utils/getParser'
|
23
|
|
|
24
|
1
|
const REQUIRE_IN_RUNTIME_PATH = absolutize('requireInRuntime')
|
25
|
1
|
const EVAL_IN_CONTEXT_PATH = absolutize('evalInContext')
|
26
|
1
|
const JSX_COMPILER_UTILS_PATH = require.resolve('vue-inbrowser-compiler-utils')
|
27
|
|
|
28
|
1
|
function isVueFile(filepath: string) {
|
29
|
1
|
return /.vue$/.test(filepath)
|
30
|
|
}
|
31
|
|
|
32
|
1
|
function isImport(req: any): req is { importPath: string; path: string } {
|
33
|
1
|
return !!req.importPath
|
34
|
|
}
|
35
|
|
|
36
|
1
|
export default function (this: StyleguidistContext, source: string) {
|
37
|
1
|
const callback = this.async()
|
38
|
1
|
const cb = callback ? callback : () => null
|
39
|
1
|
examplesLoader.call(this, source).then(res => cb(undefined, res))
|
40
|
|
}
|
41
|
|
|
42
|
1
|
export async function examplesLoader(this: StyleguidistContext, src: string): Promise<string> {
|
43
|
1
|
const filePath = this.request.split('!').pop()
|
44
|
1
|
let source: string | false = src
|
45
|
1
|
if (!filePath) {
|
46
|
0
|
return ''
|
47
|
|
}
|
48
|
1
|
if (isVueFile(filePath)) {
|
49
|
|
// if it's a vue file, the examples could be in a docs block
|
50
|
0
|
source = getComponentVueDoc(src, filePath)
|
51
|
|
}
|
52
|
1
|
const config = this._styleguidist
|
53
|
1
|
const options = loaderUtils.getOptions(this) || {}
|
54
|
1
|
const { file, displayName, shouldShowDefaultExample, customLangs } = options
|
55
|
|
|
56
|
|
// Replace placeholders (__COMPONENT__) with the passed-in component name
|
57
|
1
|
if (shouldShowDefaultExample && source) {
|
58
|
1
|
const fullFilePath = path.join(path.dirname(filePath), file)
|
59
|
1
|
const propsParser = getParser(config)
|
60
|
|
try {
|
61
|
1
|
const docs = await propsParser(fullFilePath)
|
62
|
1
|
this.addDependency(fullFilePath)
|
63
|
1
|
source = expandDefaultComponent(source, docs)
|
64
|
|
} catch (e) {
|
65
|
|
// eat the error since it will be reported by vuedoc-loader
|
66
|
|
}
|
67
|
|
}
|
68
|
|
|
69
|
1
|
const updateExample = (example: Pick<Rsg.CodeExample, 'content' | 'lang' | 'settings'>) => {
|
70
|
1
|
const p = importCodeExampleFile(example, this.resourcePath, this)
|
71
|
1
|
if (p.settings) {
|
72
|
1
|
const settings = p.settings
|
73
|
|
// code to satisfy react-styleguidist and transfer properties to the props object
|
74
|
|
// those properties will be added litterally to the wrapping div of the example
|
75
|
1
|
const props = Object.keys(settings).reduce((obj: Record<string, any> | null, key: string) => {
|
76
|
1
|
if (['style', 'className'].indexOf(key) !== -1 || /^data-/.test(key)) {
|
77
|
1
|
obj = obj || {}
|
78
|
0
|
obj[key] = settings[key]
|
79
|
0
|
delete settings[key]
|
80
|
|
}
|
81
|
1
|
return obj
|
82
|
|
}, null)
|
83
|
1
|
if (props) {
|
84
|
0
|
p.settings.props = props
|
85
|
|
}
|
86
|
|
}
|
87
|
1
|
return config.updateExample ? config.updateExample(p, this.resourcePath) : p
|
88
|
|
}
|
89
|
|
|
90
|
|
// Load examples
|
91
|
1
|
const examples = source ? chunkify(source, updateExample, customLangs) : []
|
92
|
|
|
93
|
1
|
const getExampleLiveImports = (srci: string) => getImports(getScript(srci, config.jsxInExamples))
|
94
|
|
|
95
|
|
// Find all import statements and require() calls in examples to make them
|
96
|
|
// available in webpack context at runtime.
|
97
|
|
// Note that we can't just use require() directly at runtime,
|
98
|
|
// because webpack changes its name to something like __webpack__require__().
|
99
|
1
|
const allCodeExamples = filter(examples, { type: 'code' })
|
100
|
1
|
const requiresFromExamples = allCodeExamples.reduce(
|
101
|
1
|
(requires: ({ importPath: string; path: string } | string)[], example) => {
|
102
|
1
|
const requiresLocal = getExampleLiveImports(example.content)
|
103
|
1
|
const importPath = example.settings && example.settings.importpath
|
104
|
1
|
return requires.concat(
|
105
|
1
|
importPath ? requiresLocal.map(p => ({ importPath, path: p })) : requiresLocal
|
106
|
|
)
|
107
|
|
},
|
108
|
|
[]
|
109
|
|
)
|
110
|
|
|
111
|
|
// Auto imported modules.
|
112
|
|
// We don't need to do anything here to support explicit imports: they will
|
113
|
|
// work because both imports (generated below and by rewrite-imports) will
|
114
|
|
// be eventually transpiled to `var x = require('x')`, so we'll just have two
|
115
|
|
// of them in the same scope, which is fine in non-strict mode
|
116
|
1
|
const fullContext = {
|
117
|
|
// Modules, provied by the user
|
118
|
|
...config.context,
|
119
|
|
// Append the current component module to make it accessible in examples
|
120
|
|
// without an explicit import
|
121
|
1
|
...(displayName && config.jsxInExamples ? { [displayName]: file } : {})
|
122
|
|
}
|
123
|
|
|
124
|
|
// All required or imported modules
|
125
|
1
|
const allModules = [...requiresFromExamples, ...values<string>(fullContext)]
|
126
|
|
|
127
|
|
// “Prerequire” modules required in Markdown examples and context so they
|
128
|
|
// end up in a bundle and be available at runtime
|
129
|
1
|
const allModulesCode = allModules.reduce(
|
130
|
1
|
(requires: Record<string, Record<string, any>>, requireRequest) => {
|
131
|
|
// if we are looking at a remote example
|
132
|
|
// resolve the requires from there
|
133
|
|
|
134
|
1
|
if (isImport(requireRequest)) {
|
135
|
1
|
if (!requires[requireRequest.importPath]) {
|
136
|
0
|
requires[requireRequest.importPath] = {}
|
137
|
|
}
|
138
|
0
|
const relativePath = /^\./.test(requireRequest.path)
|
139
|
1
|
? path.join(requireRequest.importPath, requireRequest.path)
|
140
|
0
|
: requireRequest.path
|
141
|
0
|
requires[requireRequest.importPath][requireRequest.path] = requireIt(relativePath)
|
142
|
|
} else {
|
143
|
1
|
requires[requireRequest] = requireIt(requireRequest)
|
144
|
|
}
|
145
|
1
|
return requires
|
146
|
|
},
|
147
|
|
{}
|
148
|
|
)
|
149
|
|
|
150
|
|
// Require context modules so they are available in an example
|
151
|
|
|
152
|
1
|
const requireContextCode = b.program(flatten(map(fullContext, resolveESModule)))
|
153
|
|
|
154
|
|
// Stringify examples object except the evalInContext function
|
155
|
1
|
const examplesWithEval = examples.map(example => {
|
156
|
1
|
if (example.type === 'code') {
|
157
|
1
|
const importPath = example.settings && example.settings.importpath
|
158
|
1
|
const evalInContext = {
|
159
|
1
|
toAST: () =>
|
160
|
1
|
b.callExpression(
|
161
|
|
b.memberExpression(b.identifier('evalInContext'), b.identifier('bind')),
|
162
|
|
[
|
163
|
|
b.identifier('null'),
|
164
|
|
b.callExpression(
|
165
|
|
b.memberExpression(b.identifier('requireInRuntime'), b.identifier('bind')),
|
166
|
1
|
[b.identifier('null'), importPath ? b.literal(importPath) : b.identifier('null')]
|
167
|
|
)
|
168
|
|
]
|
169
|
|
)
|
170
|
|
}
|
171
|
|
|
172
|
1
|
if (config.codeSplit) {
|
173
|
0
|
let compiled: any = false
|
174
|
1
|
if (process.env.NODE_ENV === 'production') {
|
175
|
|
// if we are not in prod, we want to avoid running examples through
|
176
|
|
// buble all at the same time. We then tell it to calculate on the fly
|
177
|
0
|
const compiledExample = compile(example.content, {
|
178
|
|
...config.compilerConfig,
|
179
|
1
|
...(config.jsxInExamples ? { jsx: '__pragma__(h)', objectAssign: 'concatenate' } : {})
|
180
|
|
})
|
181
|
0
|
compiled = {
|
182
|
|
...compiledExample
|
183
|
|
}
|
184
|
|
}
|
185
|
0
|
return { ...example, evalInContext, compiled }
|
186
|
|
}
|
187
|
|
|
188
|
1
|
return { ...example, evalInContext }
|
189
|
|
}
|
190
|
1
|
return example
|
191
|
|
})
|
192
|
|
|
193
|
1
|
return `
|
194
|
|
if (module.hot) {
|
195
|
|
module.hot.accept([])
|
196
|
|
}
|
197
|
|
var requireMap = ${generate(toAst(allModulesCode))};
|
198
|
|
var requireInRuntimeBase = require(${JSON.stringify(REQUIRE_IN_RUNTIME_PATH)});
|
199
|
|
var requireInRuntime = requireInRuntimeBase.bind(null, requireMap);
|
200
|
|
var evalInContextBase = require(${JSON.stringify(EVAL_IN_CONTEXT_PATH)});${
|
201
|
|
config.jsxInExamples
|
202
|
1
|
? `
|
203
|
|
|
204
|
|
var compilerUtils = require(${JSON.stringify(JSX_COMPILER_UTILS_PATH)});
|
205
|
|
var evalInContext = evalInContextBase.bind(null,
|
206
|
|
${JSON.stringify(generate(requireContextCode))},
|
207
|
|
compilerUtils.adaptCreateElement, compilerUtils.concatenate);`
|
208
|
1
|
: `
|
209
|
|
var evalInContext = evalInContextBase.bind(null,
|
210
|
|
${JSON.stringify(generate(requireContextCode))},
|
211
|
|
null, null)`
|
212
|
|
}
|
213
|
|
module.exports = ${generate(toAst(examplesWithEval))}`
|
214
|
|
}
|