diff --git a/resolvers/webpack/.eslintrc b/resolvers/webpack/.eslintrc index 743d5c949e..544167c4bb 100644 --- a/resolvers/webpack/.eslintrc +++ b/resolvers/webpack/.eslintrc @@ -3,4 +3,7 @@ "import/no-extraneous-dependencies": 1, "no-console": 1, }, + "env": { + "es6": true, + }, } diff --git a/resolvers/webpack/CHANGELOG.md b/resolvers/webpack/CHANGELOG.md index 8ab56e1a01..6485b171ee 100644 --- a/resolvers/webpack/CHANGELOG.md +++ b/resolvers/webpack/CHANGELOG.md @@ -5,8 +5,11 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## Unreleased +### Added +- add support for webpack5 'externals function' ([#2023], thanks [@jet2jet]) + ### Changed - - Add warning about async Webpack configs ([#1962], thanks [@ogonkov]) +- Add warning about async Webpack configs ([#1962], thanks [@ogonkov]) - Replace node-libs-browser with is-core-module ([#1967], thanks [@andersk]) ## 0.13.0 - 2020-09-27 @@ -141,6 +144,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - `interpret` configs (such as `.babel.js`). Thanks to [@gausie] for the initial PR ([#164], ages ago! 😅) and [@jquense] for tests ([#278]). +[#2023]: https://github.com/benmosher/eslint-plugin-import/pull/2023 [#1967]: https://github.com/benmosher/eslint-plugin-import/pull/1967 [#1962]: https://github.com/benmosher/eslint-plugin-import/pull/1962 [#1705]: https://github.com/benmosher/eslint-plugin-import/pull/1705 @@ -200,4 +204,5 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel [@migueloller]: https://github.com/migueloller [@opichals]: https://github.com/opichals [@andersk]: https://github.com/andersk -[@ogonkov]: https://github.com/ogonkov \ No newline at end of file +[@ogonkov]: https://github.com/ogonkov +[@jet2jet]: https://github.com/jet2jet \ No newline at end of file diff --git a/resolvers/webpack/index.js b/resolvers/webpack/index.js index 710630fdc7..fb03fc4d19 100644 --- a/resolvers/webpack/index.js +++ b/resolvers/webpack/index.js @@ -140,13 +140,14 @@ exports.resolve = function (source, file, settings) { log('Using config: ', webpackConfig); + const resolveSync = getResolveSync(configPath, webpackConfig, cwd); + // externals - if (findExternal(source, webpackConfig.externals, path.dirname(file))) { + if (findExternal(source, webpackConfig.externals, path.dirname(file), resolveSync)) { return { found: true, path: null }; } // otherwise, resolve "normally" - const resolveSync = getResolveSync(configPath, webpackConfig, cwd); try { return { found: true, path: resolveSync(path.dirname(file), source) }; @@ -323,7 +324,7 @@ function makeRootPlugin(ModulesInRootPlugin, name, root) { } /* eslint-enable */ -function findExternal(source, externals, context) { +function findExternal(source, externals, context, resolveSync) { if (!externals) return false; // string match @@ -331,7 +332,7 @@ function findExternal(source, externals, context) { // array: recurse if (Array.isArray(externals)) { - return externals.some(function (e) { return findExternal(source, e, context); }); + return externals.some(function (e) { return findExternal(source, e, context, resolveSync); }); } if (isRegex(externals)) { @@ -340,13 +341,45 @@ function findExternal(source, externals, context) { if (typeof externals === 'function') { let functionExternalFound = false; - externals.call(null, context, source, function(err, value) { + const callback = function (err, value) { if (err) { functionExternalFound = false; } else { - functionExternalFound = findExternal(source, value, context); + functionExternalFound = findExternal(source, value, context, resolveSync); } - }); + }; + // - for prior webpack 5, 'externals function' uses 3 arguments + // - for webpack 5, the count of arguments is less than 3 + if (externals.length === 3) { + externals.call(null, context, source, callback); + } else { + const ctx = { + context, + request: source, + contextInfo: { + issuer: '', + issuerLayer: null, + compiler: '', + }, + getResolve: () => (resolveContext, requestToResolve, cb) => { + if (cb) { + try { + cb(null, resolveSync(resolveContext, requestToResolve)); + } catch (e) { + cb(e); + } + } else { + log('getResolve without callback not supported'); + return Promise.reject(new Error('Not supported')); + } + }, + }; + const result = externals.call(null, ctx, callback); + // todo handling Promise object (using synchronous-promise package?) + if (result && typeof result.then === 'function') { + log('Asynchronous functions for externals not supported'); + } + } return functionExternalFound; } diff --git a/resolvers/webpack/test/externals.js b/resolvers/webpack/test/externals.js index 9cad635241..9c5b001eec 100644 --- a/resolvers/webpack/test/externals.js +++ b/resolvers/webpack/test/externals.js @@ -9,6 +9,13 @@ const webpack = require('../index'); const file = path.join(__dirname, 'files', 'dummy.js'); describe('externals', function () { + const settingsWebpack5 = { + config: require(path.join(__dirname, './files/webpack.config.webpack5.js')), + }; + const settingsWebpack5Async = { + config: require(path.join(__dirname, './files/webpack.config.webpack5.async-externals.js')), + }; + it('works on just a string', function () { const resolved = webpack.resolve('bootstrap', file); expect(resolved).to.have.property('found', true); @@ -32,4 +39,26 @@ describe('externals', function () { expect(resolved).to.have.property('found', true); expect(resolved).to.have.property('path', null); }); + + it('works on a function (synchronous) for webpack 5', function () { + const resolved = webpack.resolve('underscore', file, settingsWebpack5); + expect(resolved).to.have.property('found', true); + expect(resolved).to.have.property('path', null); + }); + + it('works on a function (synchronous) which uses getResolve for webpack 5', function () { + const resolved = webpack.resolve('graphql', file, settingsWebpack5); + expect(resolved).to.have.property('found', true); + expect(resolved).to.have.property('path', null); + }); + + it('prevents using an asynchronous function for webpack 5', function () { + const resolved = webpack.resolve('underscore', file, settingsWebpack5Async); + expect(resolved).to.have.property('found', false); + }); + + it('prevents using a function which uses Promise returned by getResolve for webpack 5', function () { + const resolved = webpack.resolve('graphql', file, settingsWebpack5Async); + expect(resolved).to.have.property('found', false); + }); }); diff --git a/resolvers/webpack/test/files/webpack.config.webpack5.async-externals.js b/resolvers/webpack/test/files/webpack.config.webpack5.async-externals.js new file mode 100644 index 0000000000..ba2902b83b --- /dev/null +++ b/resolvers/webpack/test/files/webpack.config.webpack5.async-externals.js @@ -0,0 +1,21 @@ +module.exports = { + externals: [ + { 'jquery': 'jQuery' }, + 'bootstrap', + async function ({ request },) { + if (request === 'underscore') { + return 'underscore' + } + }, + function ({ request, getResolve }, callback) { + if (request === 'graphql') { + const resolve = getResolve() + // dummy call (some-module should be resolved on __dirname) + resolve(__dirname, 'some-module').then( + function () { callback(null, 'graphql') }, + function (e) { callback(e) } + ) + } + }, + ], +} diff --git a/resolvers/webpack/test/files/webpack.config.webpack5.js b/resolvers/webpack/test/files/webpack.config.webpack5.js new file mode 100644 index 0000000000..88a12567a1 --- /dev/null +++ b/resolvers/webpack/test/files/webpack.config.webpack5.js @@ -0,0 +1,27 @@ +module.exports = { + externals: [ + { 'jquery': 'jQuery' }, + 'bootstrap', + function ({ request }, callback) { + if (request === 'underscore') { + return callback(null, 'underscore') + } + callback() + }, + function ({ request, getResolve }, callback) { + if (request === 'graphql') { + const resolve = getResolve() + // dummy call (some-module should be resolved on __dirname) + resolve(__dirname, 'some-module', function (err, value) { + if (err) { + callback(err) + } else { + callback(null, 'graphql') + } + }) + } else { + callback() + } + }, + ], +}