diff --git a/src/builder/WebpackConfig.js b/src/builder/WebpackConfig.js index da7a19484..a5a54a9d1 100644 --- a/src/builder/WebpackConfig.js +++ b/src/builder/WebpackConfig.js @@ -21,6 +21,7 @@ class WebpackConfig { * Build the Webpack configuration object. */ async build() { + /** @type {import("webpack").Configuration} */ this.webpackConfig = webpackDefaultConfig(); await this.buildEntry(); @@ -93,7 +94,7 @@ class WebpackConfig { return; } - const {https, host, port} = Config.hmrOptions; + const { https, host, port } = Config.hmrOptions; const protocol = https ? 'https' : 'http'; const url = `${protocol}://${host}:${port}/`; @@ -114,7 +115,7 @@ class WebpackConfig { public: url, liveReload: false, - + https, dev: { diff --git a/src/components/CssWebpackConfig.js b/src/components/CssWebpackConfig.js index 0c9a71073..7f9ca2f5f 100644 --- a/src/components/CssWebpackConfig.js +++ b/src/components/CssWebpackConfig.js @@ -171,8 +171,9 @@ class CssWebpackConfig extends AutomaticComponent { * * @param {object} [options] * @param {"auto" | "inline" | "extract"} options.method The method to use when handling CSS. + * @param {"default" | "per-file"} options.location Where these loaders are applied. The `default` set or on a per-file basis (used by preprocessors). */ - static afterLoaders({ method = 'auto' } = {}) { + static afterLoaders({ method = 'auto', location = 'default' } = {}) { const loaders = []; if (method === 'auto') { @@ -185,7 +186,11 @@ class CssWebpackConfig extends AutomaticComponent { } if (method === 'inline') { - loaders.push({ loader: 'style-loader' }); + if (Mix.components.get('vue') && location === 'default') { + loaders.push({ loader: 'vue-style-loader' }); + } else { + loaders.push({ loader: 'style-loader' }); + } } else if (method === 'extract') { loaders.push({ loader: MiniCssExtractPlugin.loader, diff --git a/src/components/Preprocessor.js b/src/components/Preprocessor.js index 37b9d8151..fb729fc00 100644 --- a/src/components/Preprocessor.js +++ b/src/components/Preprocessor.js @@ -53,7 +53,7 @@ class Preprocessor { let processUrls = this.shouldProcessUrls(preprocessor); let loaders = [ - ...CssWebpackConfig.afterLoaders({ method: 'extract' }), + ...CssWebpackConfig.afterLoaders({ method: 'extract', location: 'per-file' }), { loader: 'css-loader', options: { diff --git a/test/features/vue.js b/test/features/vue.js index 708288f91..65fdb55ef 100644 --- a/test/features/vue.js +++ b/test/features/vue.js @@ -38,6 +38,38 @@ test('it knows the Vue 2 compiler name', t => { t.true(dependencies.includes('vue-template-compiler')); }); +test('it switches to vue-style-loader when not extracting styles', async t => { + mix.vue({ version: 2, extractStyles: false }); + + const config = await webpack.buildConfig(); + + assert.hasWebpackLoader(t, config, 'vue-style-loader'); + assert.doesNotHaveWebpackLoader(t, config, 'style-loader'); + assert.doesNotHaveWebpackLoader(t, config, loader => + loader.includes('mini-css-extract-plugin') + ); +}); + +test('it does not switch to vue-style-loader when extracting styles', async t => { + mix.vue({ version: 2, extractStyles: true }); + + const config = await webpack.buildConfig(); + + assert.doesNotHaveWebpackLoader(t, config, 'vue-style-loader'); + assert.hasWebpackLoader(t, config, loader => + loader.includes('mini-css-extract-plugin') + ); +}); + +test('it does not use vue-style-loader when not using .vue', async t => { + const config = await webpack.buildConfig(); + + assert.doesNotHaveWebpackLoader(t, config, 'vue-style-loader'); + assert.hasWebpackLoader(t, config, loader => + loader.includes('mini-css-extract-plugin') + ); +}); + test('it appends vue styles to your sass compiled file', async t => { mix.vue({ version: 2, extractStyles: true }); mix.js(`test/fixtures/app/src/vue/app-with-vue-and-scss.js`, 'js/app.js').sass( diff --git a/test/features/vue3.js b/test/features/vue3.js index 5a779b938..6c1717afa 100644 --- a/test/features/vue3.js +++ b/test/features/vue3.js @@ -34,6 +34,38 @@ test('it knows the Vue 3 compiler name', t => { t.true(dependencies.includes('@vue/compiler-sfc')); }); +test('it switches to vue-style-loader when not extracting styles', async t => { + mix.vue({ version: 3, extractStyles: false }); + + const config = await webpack.buildConfig(); + + assert.hasWebpackLoader(t, config, 'vue-style-loader'); + assert.doesNotHaveWebpackLoader(t, config, 'style-loader'); + assert.doesNotHaveWebpackLoader(t, config, loader => + loader.includes('mini-css-extract-plugin') + ); +}); + +test('it does not switch to vue-style-loader when extracting styles', async t => { + mix.vue({ version: 3, extractStyles: true }); + + const config = await webpack.buildConfig(); + + assert.doesNotHaveWebpackLoader(t, config, 'vue-style-loader'); + assert.hasWebpackLoader(t, config, loader => + loader.includes('mini-css-extract-plugin') + ); +}); + +test('it does not use vue-style-loader when not using .vue', async t => { + const config = await webpack.buildConfig(); + + assert.doesNotHaveWebpackLoader(t, config, 'vue-style-loader'); + assert.hasWebpackLoader(t, config, loader => + loader.includes('mini-css-extract-plugin') + ); +}); + test('it appends vue styles to your sass compiled file', async t => { mix.vue({ version: 3, extractStyles: true }); diff --git a/test/helpers/assertions.js b/test/helpers/assertions.js index 951c1243e..c1a252a9f 100644 --- a/test/helpers/assertions.js +++ b/test/helpers/assertions.js @@ -1,6 +1,68 @@ import File from '../../src/File'; +/** + * Check that a matching webpack rule can be found + * + * @param {import("ava").Assertions} t + * @param {import("webpack").Configuration} config + * @param {(rule: import("webpack").RuleSetRule) => boolean} test + */ +function hasWebpackRule(config, test) { + /** @param {import("webpack").RuleSetRule} rule */ + const checkRule = rule => + test(rule) || + (rule.oneOf || []).find(checkRule) || + (rule.rules || []).find(checkRule); + + return !!config.module.rules.find(checkRule); +} + +/** + * Check that a matching rule with the given loader can be found + * + * @param {import("webpack").Configuration} config + * @param {string|(loader: string) => boolean} loader + */ +function hasWebpackLoader(config, loader) { + const checkLoader = typeof loader === 'string' ? str => str === loader : loader; + + return hasWebpackRule( + config, + rule => + (rule.loader && checkLoader(rule.loader)) || + (rule.use || []).find(use => checkLoader(use.loader)) + ); +} + export default { + /** + * Assert that a matching webpack rule can be found + * + * @param {import("ava").Assertions} t + * @param {import("webpack").Configuration} config + * @param {(rule: import("webpack").RuleSetRule) => boolean} test + */ + hasWebpackRule: (t, config, test) => t.true(hasWebpackRule(config, test)), + + /** + * Assert that a rule with the given loader can be found + * + * @param {import("ava").Assertions} t + * @param {import("webpack").Configuration} config + * @param {string|(loader: string) => boolean} loader + */ + hasWebpackLoader: (t, config, loader) => t.true(hasWebpackLoader(config, loader)), + + /** + * Assert that a rule with the given loader cannot be found + * + * @param {import("ava").Assertions} t + * @param {import("webpack").Configuration} config + * @param {string|(loader: string) => boolean} loader + */ + doesNotHaveWebpackLoader: (t, config, loader) => + t.false(hasWebpackLoader(config, loader)), + manifestEquals: (expected, t) => { let manifest = JSON.parse( File.find(`test/fixtures/app/dist/mix-manifest.json`).read() diff --git a/test/helpers/webpack.js b/test/helpers/webpack.js index f6d5b45ae..77abecca7 100644 --- a/test/helpers/webpack.js +++ b/test/helpers/webpack.js @@ -1,5 +1,8 @@ import webpack from 'webpack'; +/** + * @returns {Promise} + */ export async function buildConfig(shouldInit = true) { if (shouldInit) { await Mix.init();