1 | import * as _ from 'lodash'
|
2 | import * as path from 'path'
|
3 |
|
4 | import { findCompiledModule, cache } from './cache'
|
5 | import * as helpers from './helpers'
|
6 | import { QueryOptions, Loader, ensureInstance, Instance, getRootCompiler } from './instance'
|
7 | import { PathPlugin } from './paths-plugin'
|
8 | import { CheckerPlugin as _CheckerPlugin } from './watch-mode'
|
9 |
|
10 | const loaderUtils = require('loader-utils')
|
11 |
|
12 | function loader(text) {
|
13 | try {
|
14 | compiler.call(undefined, this, text)
|
15 | } catch (e) {
|
16 | console.error(e, e.stack)
|
17 | throw e
|
18 | }
|
19 | }
|
20 |
|
21 | namespace loader {
|
22 | export const TsConfigPathsPlugin = PathPlugin
|
23 | export const CheckerPlugin = _CheckerPlugin
|
24 | }
|
25 |
|
26 | interface Transformation {
|
27 | text: string
|
28 | map: any
|
29 | deps: string[]
|
30 | fresh?: boolean
|
31 | }
|
32 |
|
33 | const DECLARATION = /\.d.ts$/i
|
34 |
|
35 | function compiler(loader: Loader, text: string): void {
|
36 | if (loader.cacheable) {
|
37 | loader.cacheable()
|
38 | }
|
39 |
|
40 | const rootCompiler = getRootCompiler(loader._compiler)
|
41 |
|
42 | const query = <QueryOptions>(loaderUtils.getOptions(loader) || {})
|
43 | const options = (loader.options && loader.options.ts) || {}
|
44 | const instanceName = query.instance || 'at-loader'
|
45 | const instance = ensureInstance(loader, query, options, instanceName, rootCompiler)
|
46 | const callback = loader.async()
|
47 |
|
48 | let fileName = helpers.toUnix(loader.resourcePath)
|
49 | instance.compiledFiles[fileName] = true
|
50 |
|
51 | if (DECLARATION.test(fileName)) {
|
52 | loader.emitWarning(
|
53 | new Error(`[${instanceName}] TypeScript declaration files should never be required`)
|
54 | )
|
55 | return callback(null, '')
|
56 | }
|
57 |
|
58 | let compiledModule
|
59 | if (instance.loaderConfig.usePrecompiledFiles) {
|
60 | compiledModule = findCompiledModule(fileName)
|
61 | }
|
62 |
|
63 | let transformation: Promise<{ cached: boolean; result: Transformation }> = null
|
64 |
|
65 | if (compiledModule) {
|
66 | transformation = Promise.resolve({
|
67 | deps: [],
|
68 | text: compiledModule.text,
|
69 | map: compiledModule.map ? JSON.parse(compiledModule.map) : null
|
70 | }).then(result => ({ cached: true, result }))
|
71 | } else {
|
72 | const transformationFunction = () => transform(loader, instance, fileName, text)
|
73 |
|
74 | if (instance.loaderConfig.useCache) {
|
75 | transformation = cache<Transformation>({
|
76 | source: text,
|
77 | identifier: instance.cacheIdentifier,
|
78 | directory: instance.loaderConfig.cacheDirectory,
|
79 | options: loader.query,
|
80 | transform: transformationFunction
|
81 | })
|
82 | } else {
|
83 | transformation = transformationFunction().then(result => ({ cached: false, result }))
|
84 | }
|
85 | }
|
86 |
|
87 | transformation
|
88 | .then(async ({ cached, result }) => {
|
89 | const isolated =
|
90 | instance.loaderConfig.forceIsolatedModules ||
|
91 | instance.compilerConfig.options.isolatedModules
|
92 |
|
93 | if (!isolated && result.deps) {
|
94 |
|
95 | result.deps.forEach(dep => loader.addDependency(path.normalize(dep)))
|
96 | }
|
97 | if (cached) {
|
98 |
|
99 | const updated = await instance.checker.updateFile(fileName, text)
|
100 | if (updated) {
|
101 | if (typeof loader._module.meta.tsLoaderFileVersion === 'number') {
|
102 | loader._module.meta.tsLoaderFileVersion++
|
103 | } else {
|
104 | loader._module.meta.tsLoaderFileVersion = 0
|
105 | }
|
106 | }
|
107 | }
|
108 |
|
109 | return result
|
110 | })
|
111 | .then(({ text, map }) => {
|
112 | callback(null, text, map)
|
113 | })
|
114 | .catch(callback)
|
115 | .catch(e => {
|
116 | console.error('Error in bail mode:', e, e.stack.join ? e.stack.join('\n') : e.stack)
|
117 | process.exit(1)
|
118 | })
|
119 | }
|
120 |
|
121 | function transform(
|
122 | webpack: Loader,
|
123 | instance: Instance,
|
124 | fileName: string,
|
125 | text: string
|
126 | ): Promise<Transformation> {
|
127 | let resultText
|
128 | let resultSourceMap = null
|
129 |
|
130 | return instance.checker.emitFile(fileName, text).then(({ emitResult, deps }) => {
|
131 | resultSourceMap = emitResult.sourceMap
|
132 | resultText = emitResult.text
|
133 |
|
134 | let sourceFileName = fileName.replace(instance.context + '/', '')
|
135 | if (resultSourceMap) {
|
136 | resultSourceMap = JSON.parse(resultSourceMap)
|
137 | resultSourceMap.sources = [sourceFileName]
|
138 | resultSourceMap.file = sourceFileName
|
139 | resultSourceMap.sourcesContent = [text]
|
140 |
|
141 | resultText = resultText.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, '')
|
142 | }
|
143 |
|
144 | if (instance.loaderConfig.useBabel) {
|
145 | let defaultOptions = {
|
146 | inputSourceMap: resultSourceMap,
|
147 | sourceRoot: instance.context,
|
148 | filename: fileName,
|
149 | sourceMap: true
|
150 | }
|
151 |
|
152 | let babelOptions = _.assign({}, defaultOptions, instance.loaderConfig.babelOptions)
|
153 | let babelResult = instance.babelImpl.transform(resultText, babelOptions)
|
154 |
|
155 | resultText = babelResult.code
|
156 | resultSourceMap = babelResult.map
|
157 | }
|
158 |
|
159 | if (resultSourceMap) {
|
160 | let sourcePath = path.relative(
|
161 | instance.compilerConfig.options.sourceRoot || instance.context,
|
162 | loaderUtils.getRemainingRequest(webpack)
|
163 | )
|
164 |
|
165 | resultSourceMap.sources = [sourcePath]
|
166 | resultSourceMap.file = fileName
|
167 | resultSourceMap.sourcesContent = [text]
|
168 | }
|
169 |
|
170 | if (emitResult.declaration) {
|
171 | instance.compiledDeclarations.push(emitResult.declaration)
|
172 | }
|
173 |
|
174 | return {
|
175 | text: resultText,
|
176 | map: resultSourceMap,
|
177 | deps
|
178 | }
|
179 | })
|
180 | }
|
181 |
|
182 | export = loader
|