提交 41c6d92d 编写于 作者: J JJ Kasper 提交者: Tim Neutkens

Revert migrating babel-loader into Next.js (#7265)

clearing of stale babel cache
上级 1c20a9bf
......@@ -6,7 +6,6 @@ import {
import loadConfig from 'next-server/next-config'
import nanoid from 'next/dist/compiled/nanoid/index.js'
import path from 'path'
import fs from 'fs'
import formatWebpackMessages from '../client/dev-error-overlay/format-webpack-messages'
import { recursiveDelete } from '../lib/recursive-delete'
......@@ -27,10 +26,7 @@ import getBaseWebpackConfig from './webpack-config'
import { exportManifest, getPageChunks } from './webpack/plugins/chunk-graph-plugin'
import { writeBuildId } from './write-build-id'
import { recursiveReadDir } from '../lib/recursive-readdir'
import { usedBabelCacheFiles } from './webpack/loaders/next-babel-loader/cache'
import { promisify } from 'util'
const unlink = promisify(fs.unlink)
export default async function build(dir: string, conf = null): Promise<void> {
if (!(await isWriteable(dir))) {
......@@ -284,19 +280,5 @@ export default async function build(dir: string, conf = null): Promise<void> {
await flyingShuttle.save()
}
// to prevent persisted caches from growing massively
// we clear un-used files
const babelCacheDir = path.join(distDir, 'cache/next-babel-loader')
let babelCacheToClear = (await recursiveReadDir(babelCacheDir, /.*\.js/))
.map(file => path.join(babelCacheDir, file))
babelCacheToClear = babelCacheToClear.filter((file: string) => {
return !usedBabelCacheFiles.has(file)
})
for (const file of babelCacheToClear) {
await unlink(file)
}
await writeBuildId(distDir, buildId, selectivePageBuilding)
}
import hash from 'string-hash'
import { join, basename } from 'path'
import babelLoader from 'babel-loader'
// increment 'c' to invalidate cache
const cacheKey = 'babel-cache-' + 'c' + '-'
module.exports = babelLoader.custom(babel => {
const presetItem = babel.createConfigItem(require('../../babel/preset'), { type: 'preset' })
const applyCommonJs = babel.createConfigItem(require('../../babel/plugins/commonjs'), { type: 'plugin' })
const commonJsItem = babel.createConfigItem(require('@babel/plugin-transform-modules-commonjs'), { type: 'plugin' })
const configs = new Set()
return {
customOptions (opts) {
const custom = {
isServer: opts.isServer,
asyncToPromises: opts.asyncToPromises
}
const filename = join(opts.cwd, 'noop.js')
const loader = Object.assign({
cacheCompression: false,
cacheDirectory: join(opts.distDir, 'cache', 'next-babel-loader'),
cacheIdentifier: cacheKey + JSON.stringify(
babel.loadPartialConfig({
filename,
cwd: opts.cwd,
sourceFileName: filename
}).options
)
}, opts)
delete loader.isServer
delete loader.asyncToPromises
delete loader.distDir
return { loader, custom }
},
config (cfg, { source, customOptions: { isServer, asyncToPromises } }) {
const filename = this.resourcePath
const options = Object.assign({}, cfg.options)
if (cfg.hasFilesystemConfig()) {
for (const file of [cfg.babelrc, cfg.config]) {
// We only log for client compilation otherwise there will be double output
if (file && !isServer && !configs.has(file)) {
configs.add(file)
console.log(`> Using external babel configuration`)
console.log(`> Location: "${file}"`)
}
}
} else {
// Add our default preset if the no "babelrc" found.
options.presets = [...options.presets, presetItem]
}
if (!isServer && source.indexOf('next/amp')) {
const dropClientPlugin = babel.createConfigItem([require('../../babel/plugins/next-drop-client-page'), {}], { type: 'plugin' })
options.plugins = options.plugins || []
options.plugins.push(dropClientPlugin)
}
if (isServer && source.indexOf('next/data') !== -1) {
const nextDataPlugin = babel.createConfigItem([require('../../babel/plugins/next-data'), { key: basename(filename) + '-' + hash(filename) }], { type: 'plugin' })
options.plugins = options.plugins || []
options.plugins.push(nextDataPlugin)
}
if (asyncToPromises) {
const asyncToPromisesPlugin = babel.createConfigItem(['babel-plugin-transform-async-to-promises', {
inlineHelpers: true
}], { type: 'plugin' })
options.plugins = options.plugins || []
options.plugins.push(asyncToPromisesPlugin)
const regeneratorPlugin = options.plugins.find(plugin => {
return plugin[0] === require('@babel/plugin-transform-runtime')
})
if (regeneratorPlugin) {
regeneratorPlugin[1].regenerator = false
}
const babelPresetEnv = (options.presets || []).find((preset = []) => {
return preset[0] === require('@babel/preset-env').default
})
if (babelPresetEnv) {
babelPresetEnv[1].exclude = (options.presets[0][1].exclude || []).concat([
'transform-typeof-symbol',
'transform-regenerator',
'transform-async-to-generator'
])
.filter('transform-typeof-symbol')
}
}
// If the file has `module.exports` we have to transpile commonjs because Babel adds `import` statements
// That break webpack, since webpack doesn't support combining commonjs and esmodules
if (source.indexOf('module.exports') !== -1) {
options.plugins = options.plugins || []
options.plugins.push(applyCommonJs)
}
// As next-server/lib has stateful modules we have to transpile commonjs
options.overrides = [
...(options.overrides || []),
{
test: [
/next-server[\\/]dist[\\/]lib/,
/next[\\/]dist[\\/]client/,
/next[\\/]dist[\\/]pages/
],
plugins: [
commonJsItem
]
}
]
return options
}
}
})
import hash from 'string-hash'
import * as babel from '@babel/core'
import { loader } from 'webpack'
import { join, basename } from 'path'
import loaderUtils from 'loader-utils'
import cache from './next-babel-loader/cache'
import injectCaller from './next-babel-loader/injectCaller'
import transform, { version as transformVersion } from './next-babel-loader/transform'
// increment 'a' to invalidate cache
const cacheKey = 'babel-cache-' + 'b' + '-'
const configs = new Set()
const presetItem = babel.createConfigItem(require('../../babel/preset'), { type: 'preset' })
const applyCommonJs = babel.createConfigItem(require('../../babel/plugins/commonjs'), { type: 'plugin' })
const commonJsItem = babel.createConfigItem(require('@babel/plugin-transform-modules-commonjs'), { type: 'plugin' })
const nextBabelLoader: loader.Loader = function (source, inputSourceMap) {
const callback = this.async()!
const filename = this.resourcePath;
let loaderOptions = loaderUtils.getOptions(this) || {};
const { isServer, asyncToPromises, distDir } = loaderOptions
// Standardize on 'sourceMaps' as the key passed through to Webpack, so that
// users may safely use either one alongside our default use of
// 'this.sourceMap' below without getting error about conflicting aliases.
if (
Object.prototype.hasOwnProperty.call(loaderOptions, "sourceMap") &&
!Object.prototype.hasOwnProperty.call(loaderOptions, "sourceMaps")
) {
loaderOptions = Object.assign({}, loaderOptions, {
sourceMaps: loaderOptions.sourceMap,
});
delete loaderOptions.sourceMap;
}
const programmaticOptions = Object.assign({}, loaderOptions, {
filename,
inputSourceMap: inputSourceMap || undefined,
// Set the default sourcemap behavior based on Webpack's mapping flag,
// but allow users to override if they want.
sourceMaps:
loaderOptions.sourceMaps === undefined
? this.sourceMap
: loaderOptions.sourceMaps,
// Ensure that Webpack will get a full absolute path in the sourcemap
// so that it can properly map the module back to its internal cached
// modules.
sourceFileName: filename,
});
// Remove loader related options
delete programmaticOptions.cacheDirectory;
delete programmaticOptions.cacheIdentifier;
delete programmaticOptions.cacheCompression;
delete programmaticOptions.isServer
delete programmaticOptions.asyncToPromises
delete programmaticOptions.distDir
const config: any = babel.loadPartialConfig(injectCaller(programmaticOptions));
if (config) {
let options = config.options;
if (config.hasFilesystemConfig()) {
for (const file of [config.babelrc, config.config]) {
// We only log for client compilation otherwise there will be double output
if (file && !isServer && !configs.has(file)) {
configs.add(file)
console.log(`> Using external babel configuration`)
console.log(`> Location: "${file}"`)
}
}
} else {
// Add our default preset if the no "babelrc" found.
options.presets = [...options.presets, presetItem]
}
if (!isServer && source.toString().indexOf('next/amp')) {
const dropClientPlugin = babel.createConfigItem([require('../../babel/plugins/next-drop-client-page'), {}], { type: 'plugin' })
options.plugins = options.plugins || []
options.plugins.push(dropClientPlugin)
}
if (isServer && source.toString().indexOf('next/data') !== -1) {
const nextDataPlugin = babel.createConfigItem([require('../../babel/plugins/next-data'), { key: basename(filename) + '-' + hash(filename) }], { type: 'plugin' })
options.plugins = options.plugins || []
options.plugins.push(nextDataPlugin)
}
if (asyncToPromises) {
const asyncToPromisesPlugin = babel.createConfigItem(['babel-plugin-transform-async-to-promises', {
inlineHelpers: true
}], { type: 'plugin' })
options.plugins = options.plugins || []
options.plugins.push(asyncToPromisesPlugin)
const regeneratorPlugin = options.plugins.find((plugin: any) => {
return plugin[0] === require('@babel/plugin-transform-runtime')
})
if (regeneratorPlugin) {
regeneratorPlugin[1].regenerator = false
}
const babelPresetEnv = (options.presets || []).find((preset: any = []) => {
return preset[0] === require('@babel/preset-env').default
})
if (babelPresetEnv) {
babelPresetEnv[1].exclude = (options.presets[0][1].exclude || []).concat([
'transform-typeof-symbol',
'transform-regenerator',
'transform-async-to-generator'
])
.filter('transform-typeof-symbol')
}
}
// If the file has `module.exports` we have to transpile commonjs because Babel adds `import` statements
// That break webpack, since webpack doesn't support combining commonjs and esmodules
if (source.toString().indexOf('module.exports') !== -1) {
options.plugins = options.plugins || []
options.plugins.push(applyCommonJs)
}
// As next-server/lib has stateful modules we have to transpile commonjs
options.overrides = [
...(options.overrides || []),
{
test: [
/next-server[\\/]dist[\\/]lib/,
/next[\\/]dist[\\/]client/,
/next[\\/]dist[\\/]pages/
],
plugins: [
commonJsItem
]
}
]
if (options.sourceMaps === "inline") {
// Babel has this weird behavior where if you set "inline", we
// inline the sourcemap, and set 'result.map = null'. This results
// in bad behavior from Babel since the maps get put into the code,
// which Webpack does not expect, and because the map we return to
// Webpack is null, which is also bad. To avoid that, we override the
// behavior here so "inline" just behaves like 'true'.
options.sourceMaps = true;
}
const {
cacheDirectory = join(distDir, 'cache', 'next-babel-loader'),
cacheIdentifier = JSON.stringify({
options,
cacheKey,
"@babel/core": transformVersion,
})
} = loaderOptions;
const addDependency = (dep: string) => this.addDependency(dep)
async function getResult() {
let result;
if (cacheDirectory) {
result = await cache({
source,
options,
transform,
cacheDirectory,
cacheIdentifier,
});
} else {
result = await transform(source.toString(), options);
}
// TODO: Babel should really provide the full list of config files that
// were used so that this can also handle files loaded with 'extends'.
if (typeof config.babelrc === "string") {
addDependency(config.babelrc);
}
if (result) {
const { code, map } = result;
callback(null, code, map)
return
}
}
getResult.bind(this)().catch((err: any) => callback(err))
return
}
// If the file was ignored, pass through the original content.
callback(null, source, inputSourceMap);
return
}
export default nextBabelLoader
const STRIP_FILENAME_RE = /^[^:]+: /;
const format = (err: any) => {
if (err instanceof SyntaxError) {
err.name = "SyntaxError";
err.message = err.message.replace(STRIP_FILENAME_RE, "");
// @ts-ignore
err.hideStack = true;
} else if (err instanceof TypeError) {
// @ts-ignore
err.name = null;
err.message = err.message.replace(STRIP_FILENAME_RE, "");
// @ts-ignore
err.hideStack = true;
}
return err;
};
export default class LoaderError extends Error {
name: string
hideStack: any
constructor(err: any) {
super();
const { name, message, codeFrame, hideStack } = format(err);
this.name = "BabelLoaderError";
this.message = `${name ? `${name}: ` : ""}${message}\n\n${codeFrame}\n`;
this.hideStack = hideStack;
Error.captureStackTrace(this, this.constructor);
}
}
Copyright (c) 2014-2019 Luís Couto <hello@luiscouto.pt>
MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
/**
* Filesystem Cache
*
* Given a file and a transform function, cache the result into files
* or retrieve the previously cached files if the given file is already known.
*
* @see https://github.com/babel/babel-loader/issues/34
* @see https://github.com/babel/babel-loader/pull/41
*/
import fs from "fs"
import os from "os"
import path from "path"
import crypto from "crypto"
import mkdirpOrig from "mkdirp"
import { promisify } from "util"
import transform from './transform'
// Lazily instantiated when needed
let defaultCacheDirectory: any = null;
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);
const mkdirp = promisify(mkdirpOrig);
export const usedBabelCacheFiles: Set<String> = new Set()
/**
* Read the contents from the compressed file.
*
* @async
* @params {String} filename
*/
async function read(filename: string) {
const data = await readFile(filename);
const content = data;
usedBabelCacheFiles.add(filename)
return JSON.parse(content.toString());
};
/**
* Write contents into a compressed file.
*/
async function write(filename: string, result: any) {
const content = JSON.stringify(result);
usedBabelCacheFiles.add(filename)
return await writeFile(filename, content);
};
/**
* Build the filename for the cached file
*/
function filename(source: string, identifier: any, options: any): string {
const hash = crypto.createHash("md4");
const contents = JSON.stringify({ source, options, identifier });
hash.update(contents);
return hash.digest("hex") + ".json";
};
/**
* Handle the cache
*/
async function handleCache(directory: string, params: any): Promise<any> {
const {
source,
options = {},
cacheIdentifier,
cacheDirectory,
} = params;
const file = path.join(directory, filename(source, cacheIdentifier, options));
try {
// No errors mean that the file was previously cached
// we just need to return it
return await read(file);
} catch (err) {}
const fallback =
typeof cacheDirectory !== "string" && directory !== os.tmpdir();
// Make sure the directory exists.
try {
await mkdirp(directory);
} catch (err) {
if (fallback) {
return handleCache(os.tmpdir(), params);
}
throw err;
}
// Otherwise just transform the file
// return it to the user asap and write it in cache
const result = await transform(source, options);
try {
await write(file, result);
} catch (err) {
if (fallback) {
// Fallback to tmpdir if node_modules folder not writable
return handleCache(os.tmpdir(), params);
}
throw err;
}
return result;
};
/**
* Retrieve file from cache, or create a new one for future reads
*
* @async
* @param {Object} params
* @param {String} params.directory Directory to store cached files
* @param {String} params.identifier Unique identifier to bust cache
* @param {String} params.source Original contents of the file to be cached
* @param {Object} params.options Options to be given to the transform fn
* @param {Function} params.transform Function that will transform the
* original file and whose result will be
* cached
*
* @example
*
* cache({
* directory: '.tmp/cache',
* identifier: 'babel-loader-cachefile',
* source: *source code from file*,
* options: {
* experimental: true,
* runtime: true
* },
* transform: function(source, options) {
* var content = *do what you need with the source*
* return content;
* }
* }, function(err, result) {
*
* });
*/
export default async function cache(params: any) {
let directory;
if (typeof params.cacheDirectory === "string") {
directory = params.cacheDirectory;
} else {
directory = defaultCacheDirectory;
}
return await handleCache(directory, params);
};
import * as babel from "@babel/core"
export default function injectCaller (opts: any) {
if (!supportsCallerOption()) return opts;
return Object.assign({}, opts, {
caller: Object.assign(
{
name: "babel-loader",
// Webpack >= 2 supports ESM and dynamic import.
supportsStaticESM: true,
supportsDynamicImport: true,
},
opts.caller,
),
});
};
// TODO: We can remove this eventually, I'm just adding it so that people have
// a little time to migrate to the newer RCs of @babel/core without getting
// hard-to-diagnose errors about unknown 'caller' options.
let supportsCallerOptionFlag: any = undefined;
function supportsCallerOption() {
if (supportsCallerOptionFlag === undefined) {
try {
// Rather than try to match the Babel version, we just see if it throws
// when passed a 'caller' flag, and use that to decide if it is supported.
babel.loadPartialConfig({
// @ts-ignore
caller: undefined,
babelrc: false,
configFile: false,
});
supportsCallerOptionFlag = true;
} catch (err) {
supportsCallerOptionFlag = false;
}
}
return supportsCallerOptionFlag;
}
import LoaderError from './Error'
import * as babel from '@babel/core'
export default async function transform(source: string, options: any) {
let result;
try {
result = await babel.transformAsync(source, options);
} catch (err) {
throw err.message && err.codeFrame ? new LoaderError(err) : err;
}
if (!result) return null;
// We don't return the full result here because some entries are not
// really serializable. For a full list of properties see here:
// https://github.com/babel/babel/blob/master/packages/babel-core/src/transformation/index.js
// For discussion on this topic see here:
// https://github.com/babel/babel-loader/pull/629
const { ast, code, map, metadata } = result;
if (map && (!map.sourcesContent || !map.sourcesContent.length)) {
map.sourcesContent = [source];
}
return { ast, code, map, metadata };
};
export const version = babel.version;
......@@ -70,6 +70,7 @@
"async-sema": "2.2.0",
"autodll-webpack-plugin": "0.4.2",
"babel-core": "7.0.0-bridge.0",
"babel-loader": "8.0.5",
"babel-plugin-react-require": "3.0.0",
"babel-plugin-transform-async-to-promises": "0.8.9",
"babel-plugin-transform-react-remove-prop-types": "0.4.15",
......
......@@ -2556,6 +2556,16 @@ babel-jest@23.6.0, babel-jest@^23.6.0:
babel-plugin-istanbul "^4.1.6"
babel-preset-jest "^23.2.0"
babel-loader@8.0.5:
version "8.0.5"
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.5.tgz#225322d7509c2157655840bba52e46b6c2f2fe33"
integrity sha512-NTnHnVRd2JnRqPC0vW+iOQWU5pchDbYXsG2E6DMXEpMfUcQKclF9gmf3G3ZMhzG7IG9ji4coL0cm+FxeWxDpnw==
dependencies:
find-cache-dir "^2.0.0"
loader-utils "^1.0.2"
mkdirp "^0.5.1"
util.promisify "^1.0.0"
babel-messages@^6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册