UNPKG

4.96 kBPlain TextView Raw
1import * as _ from 'lodash'
2import * as path from 'path'
3
4import { findCompiledModule, cache } from './cache'
5import * as helpers from './helpers'
6import { QueryOptions, Loader, ensureInstance, Instance, getRootCompiler } from './instance'
7import { PathPlugin } from './paths-plugin'
8import { CheckerPlugin as _CheckerPlugin } from './watch-mode'
9
10const loaderUtils = require('loader-utils')
11
12function 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
21namespace loader {
22 export const TsConfigPathsPlugin = PathPlugin
23 export const CheckerPlugin = _CheckerPlugin
24}
25
26interface Transformation {
27 text: string
28 map: any
29 deps: string[]
30 fresh?: boolean
31}
32
33const DECLARATION = /\.d.ts$/i
34
35function 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 // If our modules are isolated we don"t need to recompile all the deps
95 result.deps.forEach(dep => loader.addDependency(path.normalize(dep)))
96 }
97 if (cached) {
98 // Update file in checker in case we read it from the cache
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
121function 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
182export = loader