extract option doesn't work
Hi,
I am getting this error:
TypeError: Cannot read property 'publicPath' of undefined
at Vuetify.addExtract (H:\Projects\Homestead\test1\node_modules\vuetifyjs-mix-extension\index.js:106:43)
at Vuetify.webpackConfig (H:\Projects\Homestead\test1\node_modules\vuetifyjs-mix-extension\index.js:83:38)
at H:\Projects\Homestead\test1\node_modules\laravel-mix\src\components\ComponentRegistrar.js:106:54
at H:\Projects\Homestead\test1\node_modules\laravel-mix\src\Dispatcher.js:37:62
at Array.map (<anonymous>)
at Dispatcher.fire (H:\Projects\Homestead\test1\node_modules\laravel-mix\src\Dispatcher.js:37:47)
at Mix.dispatch (H:\Projects\Homestead\test1\node_modules\laravel-mix\src\Mix.js:224:32)
at WebpackConfig.build (H:\Projects\Homestead\test1\node_modules\laravel-mix\src\builder\WebpackConfig.js:36:19)
at async H:\Projects\Homestead\test1\node_modules\webpack-cli\lib\webpack-cli.js:908:37
at async Promise.all (index 0)
at async evaluateConfig (H:\Projects\Homestead\test1\node_modules\webpack-cli\lib\webpack-cli.js:899:35)
at async Promise.all (index 0)
at async WebpackCLI.resolveConfig (H:\Projects\Homestead\test1\node_modules\webpack-cli\lib\webpack-cli.js:930:38)
at async WebpackCLI.createCompiler (H:\Projects\Homestead\test1\node_modules\webpack-cli\lib\webpack-cli.js:1329:22)
at async WebpackCLI.bundleCommand (H:\Projects\Homestead\test1\node_modules\webpack-cli\lib\webpack-cli.js:1433:20)
at async Command.<anonymous> (H:\Projects\Homestead\test1\node_modules\webpack-cli\lib\webpack-cli.js:384:25)
when I use extract css option.
Here is my webpack config:
const mix = require('laravel-mix');
const path = require('path');
// const cssImport = require('postcss-import');
// const cssNesting = require('postcss-nesting');
require('vuetifyjs-mix-extension');
mix.options({
terser: {
terserOptions: {
compress: {
drop_console: true
}
}
}
});
mix
.js('resources/js/app.js', 'public/js')
.vuetify('vuetify-loader', {
extract: 'css/app-v.css'
})
.vue({
extractStyles: true
})
.webpackConfig({
resolve: {
alias: {
'@': path.resolve('resources/js'),
},
},
})
.copy('resources/assets/img', 'public/img')
.disableNotifications();
This is really strange, are you working with laravel ? or a standalone project, and can you share your package.json?
I'm having similar issues working with Laravel Mix.
With the following option in webpack.mix.js:
.vuetify('vuetify-loader', {
extract: 'css/capture.css'
})
I get the following error:
[webpack-cli] ValidationError: Invalid options object. Mini CSS Extract Plugin has been initialized using an options object that does not match the API schema.
- options has an unknown property 'options'. These properties are valid:
object { filename?, chunkFilename?, ignoreOrder?, insert?, attributes?, linkType? }
at validate (/var/www/html/node_modules/schema-utils/dist/validate.js:104:11)
at new MiniCssExtractPlugin (/var/www/html/node_modules/mini-css-extract-plugin/dist/index.js:47:31)
at Vuetify.addExtract (/var/www/html/node_modules/vuetifyjs-mix-extension/index.js:100:13)
at Vuetify.webpackConfig (/var/www/html/node_modules/vuetifyjs-mix-extension/index.js:83:38)
at /var/www/html/node_modules/laravel-mix/src/components/ComponentRegistrar.js:106:54
at /var/www/html/node_modules/laravel-mix/src/Dispatcher.js:37:62
at Array.map (<anonymous>)
at Dispatcher.fire (/var/www/html/node_modules/laravel-mix/src/Dispatcher.js:37:47)
at Mix.dispatch (/var/www/html/node_modules/laravel-mix/src/Mix.js:224:32)
at WebpackConfig.build (/var/www/html/node_modules/laravel-mix/src/builder/WebpackConfig.js:36:19)
at async /var/www/html/node_modules/webpack-cli/lib/webpack-cli.js:908:37
at async Promise.all (index 0)
at async evaluateConfig (/var/www/html/node_modules/webpack-cli/lib/webpack-cli.js:899:35)
at async Promise.all (index 0)
at async WebpackCLI.resolveConfig (/var/www/html/node_modules/webpack-cli/lib/webpack-cli.js:930:38)
at async WebpackCLI.createCompiler (/var/www/html/node_modules/webpack-cli/lib/webpack-cli.js:1329:22) {
errors: [
{
keyword: 'additionalProperties',
dataPath: '',
schemaPath: '#/additionalProperties',
params: [Object],
message: 'should NOT have additional properties',
schema: false,
parentSchema: [Object],
data: [Object]
}
],
schema: {
type: 'object',
additionalProperties: false,
properties: {
filename: [Object],
chunkFilename: [Object],
ignoreOrder: [Object],
insert: [Object],
attributes: [Object],
linkType: [Object]
}
},
headerName: 'Mini CSS Extract Plugin',
baseDataPath: 'options',
postFormatter: null
}
@kaysersoze That's something I am fixing atm.
No worries. Thanks for your help and your work!
@Nothing-Works I am working with Laravel, it happens even if my app.js is empty, here is my packages.json:
{
"private": true,
"scripts": {
"dev": "npm run development",
"development": "mix",
"watch": "mix watch",
"watch-poll": "mix watch -- --watch-options-poll=1000",
"hot": "mix watch --hot",
"prod": "npm run production",
"production": "mix --production"
},
"devDependencies": {
"@inertiajs/inertia": "^0.8.2",
"@inertiajs/inertia-vue": "^0.5.4",
"@mdi/js": "^5.8.55",
"axios": "^0.21.1",
"deepmerge": "^4.2.2",
"postcss": "^8.2.4",
"laravel-mix": "^6.0.11",
"sass": "^1.30.0",
"sass-loader": "^9.0.3",
"vue": "^2.6.12",
"vue-template-compiler": "^2.6.12",
"vuetify-loader": "1.7.1",
"vuetifyjs-mix-extension": "^0.0.17"
},
"dependencies": {
"@inertiajs/progress": "^0.2.3",
"@vue/composition-api": "^1.0.0-beta.25",
"lodash": "^4.17.20",
"vee-validate": "^3.4.5",
"vue-loader": "^15.9.5",
"vue-meta": "^2.4.0",
"vuetify": "^2.4.0",
"vuex": "^3.6.0"
}
}
I have updated vuetifyjs-mix-extension to v0.0.19 and the error "Cannot read property 'publicPath' of undefined" is gone but there is new one now:
Error: Conflict: Multiple chunks emit assets to the same filename css/app-v.css (chunks /js/app and css\vue-styles)
at H:\Projects\Homestead\test1\node_modules\webpack\lib\Compilation.js:3497:12
at H:\Projects\Homestead\test1\node_modules\webpack\lib\Cache.js:93:5
at Hook.eval [as callAsync] (eval at create (H:\Projects\Homestead\test1\node_modules\tapable\lib\HookCodeFactory.js:33:10), <anonymous>:16:1)
at Cache.get (H:\Projects\Homestead\test1\node_modules\webpack\lib\Cache.js:75:18)
at ItemCacheFacade.get (H:\Projects\Homestead\test1\node_modules\webpack\lib\CacheFacade.js:117:15)
at H:\Projects\Homestead\test1\node_modules\webpack\lib\Compilation.js:3443:22
at arrayEach (H:\Projects\Homestead\test1\node_modules\neo-async\async.js:2405:9)
at Object.each (H:\Projects\Homestead\test1\node_modules\neo-async\async.js:2846:9)
at H:\Projects\Homestead\test1\node_modules\webpack\lib\Compilation.js:3432:14
at symbolEach (H:\Projects\Homestead\test1\node_modules\neo-async\async.js:2444:9)
at Object.each (H:\Projects\Homestead\test1\node_modules\neo-async\async.js:2849:16)
at Compilation.createChunkAssets (H:\Projects\Homestead\test1\node_modules\webpack\lib\Compilation.js:3410:12)
at H:\Projects\Homestead\test1\node_modules\webpack\lib\Compilation.js:2241:14
at H:\Projects\Homestead\test1\node_modules\webpack\lib\Compilation.js:2377:5
at H:\Projects\Homestead\test1\node_modules\neo-async\async.js:2818:7
at done (H:\Projects\Homestead\test1\node_modules\neo-async\async.js:3522:9)
@lukkur @kaysersoze Hi sorry about the bugs, v0.0.20 should resolve all the errors, however it generates an extra file within your js folder, which I think is a laravel-mix bug and I logged here. If anyone can help that would be great.
@Nothing-Works I got this error. I use v0.0.20
[webpack-cli] Error: Conflict: Multiple chunks emit assets to the same filename css/vuetify-components.css (chunks /js/app and css/app)
at C:\laragon\www\vuetify\node_modules\webpack\lib\Compilation.js:3497:12
at C:\laragon\www\vuetify\node_modules\webpack\lib\Cache.js:93:5
at Hook.eval [as callAsync] (eval at create (C:\laragon\www\vuetify\node_modules\tapable\lib\HookCodeFactory.js:33:10), <anonymous>:16:1)
at Cache.get (C:\laragon\www\vuetify\node_modules\webpack\lib\Cache.js:75:18)
at ItemCacheFacade.get (C:\laragon\www\vuetify\node_modules\webpack\lib\CacheFacade.js:117:15)
at C:\laragon\www\vuetify\node_modules\webpack\lib\Compilation.js:3443:22
at arrayEach (C:\laragon\www\vuetify\node_modules\neo-async\async.js:2405:9)
at Object.each (C:\laragon\www\vuetify\node_modules\neo-async\async.js:2846:9)
at C:\laragon\www\vuetify\node_modules\webpack\lib\Compilation.js:3432:14
at symbolEach (C:\laragon\www\vuetify\node_modules\neo-async\async.js:2444:9)
at Object.each (C:\laragon\www\vuetify\node_modules\neo-async\async.js:2849:16)
at Compilation.createChunkAssets (C:\laragon\www\vuetify\node_modules\webpack\lib\Compilation.js:3410:12)
at C:\laragon\www\vuetify\node_modules\webpack\lib\Compilation.js:2241:14
at C:\laragon\www\vuetify\node_modules\webpack\lib\Compilation.js:2377:5
at C:\laragon\www\vuetify\node_modules\neo-async\async.js:2818:7
at done (C:\laragon\www\vuetify\node_modules\neo-async\async.js:3522:9)
@Nothing-Works I'm getting the same error as @jump-style above when using v0.0.20. Working around it for now (I'm just importing all of Vuetify scss via my own scss file), but would love to be able to do the extraction whenever that becomes possible.
I'm currently trying to get an initial version of a project using this out the door, but once I do that, I can help look into why that laravel-mix bug is happening and do some more in-depth debugging.
Thanks again for all of your work. It is appreciated.
@kaysersoze @jump-style @lukkur I am sorry I cannot really reproduce this seems ok when I tested it. Can any of you provide a repo with the minimum code that has this problem so that is easy for me to debug, please?
@Nothing-Works please try this https://github.com/lukkur/example I commented some code in app.js, I hope it helps :)
Hi, I am getting the same error
Error: Conflict: Multiple chunks emit assets to the same filename public/css/vuetify/vuetify-components.css (chunks css/admin and css/app)
I see @lukkur already provided a test repo. Since after update the not-extracted css files also don't work and this was a workaround, is there any way to fix it for now - or do I just manually import the css files?
Still getting [webpack-cli] Error: Conflict: Multiple chunks emit assets to the same filename public/css/vuetify.css (chunks css/app and js/vue) anybody got a workaround?
After much struggle, it seems the issue is caused when you add
.sass('resources/sass/app.scss', 'public/css')
The problem being that the extract name is not respected
.vuetify('vuetify-loader', {
extract: 'css/vuetify.css',
})
Will output to /public/css/app.css instead of vuetify.css
So the problem is caused because there is 2 MiniCssExtract plugin, the second addition is not needed, there should be a check, if it's already present in the plugins list then don't add it
Then to actually get the file to be output in the correct place it's missing a chunk definition
const mix = require('laravel-mix')
const Chunks = require('laravel-mix/src/Chunks').Chunks;
const File = require('laravel-mix/src/File');
const getPath = require('./src/getPath')
const resolveOptions = require('./src/resolveOptions')
const path = require('path')
class Vuetify {
constructor() {
this.vuetifyPath = getPath('node_modules/vuetify')
}
withVuetifyLoader() {
return this.vuetifyLoader === 'vuetify-loader'
}
withExtract() {
return !!this.extract
}
withPostcss() {
return !!this.postcss
}
register(loader, ...options) {
this.vuetifyLoader = loader
this.resolve(options)
}
resolve(options) {
const resolved = resolveOptions(options)
this.vuetifyLoaderOptions = resolved.vuetifyLoaderOptions
this.sassArray = resolved.sassArray
this.extract = resolved.extract
this.postcss = resolved.postcss
}
dependencies() {
this.requiresReload = true
const deps = ['vuetify', 'sass', 'sass-loader', 'deepmerge']
if (this.withVuetifyLoader()) deps.push('vuetify-loader@next')
if (this.withExtract()) deps.push('mini-css-extract-plugin')
if (this.withPostcss()) deps.push('postcss-loader')
return deps
}
generateRules() {
return this.sassArray.map((t) => ({
test: t.sass,
include: [this.vuetifyPath],
use: [
this.withExtract()
? require('mini-css-extract-plugin').loader
: 'vue-style-loader',
'css-loader',
...this.addPostcssIfNeeded(),
{
loader: 'sass-loader',
options: {
additionalData: t.data,
implementation: require('sass'),
sassOptions: {
indentedSyntax: true
}
}
}
]
}))
}
webpackRules() {
return this.generateRules()
}
webpackConfig(config) {
this.excludeVuetifyPath(config)
if (this.withVuetifyLoader()) this.addVuetifyLoader(config)
if (this.withExtract()) this.addExtract(config)
}
addPostcssIfNeeded() {
return this.withPostcss() ? ['postcss-loader'] : []
}
addVuetifyLoader(config) {
const VuetifyLoaderPlugin = require('vuetify-loader/lib/plugin')
config.plugins.push(new VuetifyLoaderPlugin(this.vuetifyLoaderOptions))
}
updateChunks(config) {
const chunks = Chunks.instance();
const re = /(?<!node_modules)[\\/]node_modules[\\/](vuetify[\\/])/i;
const groups = config.optimization.splitChunks.cacheGroups;
Object.keys(groups).forEach((k) => {
if (typeof groups[k] === 'object') {
if (groups[k].type === 'css/mini-extract' && groups[k].chunks === 'all') {
const orig = groups[k].test;
groups[k].test = (module, context) => {
const name = module.nameForCondition();
return name && !re.test(name) && orig(module, context);
};
}
}
})
const output = new File(this.extract);
chunks.add(
'styles-vuetify',
output.normalizedOutputPath(),
[re, module => module.type === 'css/mini-extract'],
{
chunks: 'all',
enforce: true,
type: 'css/mini-extract'
}
);
}
addExtract(config) {
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const plugin = config.plugins.find((p) => p instanceof MiniCssExtractPlugin);
if (!plugin) {
config.plugins.push(
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[name].css',
ignoreOrder: true,
})
)
} else {
plugin.options.ignoreOrder = true;
}
this.updateChunks(config);
}
excludeVuetifyPath(config) {
for (const i of this.sassArray)
config.module.rules
.find((r) => String(r.test) === String(i.sass))
.exclude.push(this.vuetifyPath)
}
}
mix.extend('vuetify', new Vuetify())
Just noting that if you also do .extract('vuetify') you'll get the css in js/vendor.css and not the path specified in the extract option
@tofandel
In my case, it doesn't go into the vendor.css - but it's included inside the .js file... I am doing a mix-extract as follows:
.extract(['vuetify'], 'js/vendor/vuetify.js')
So the problem is caused because there is 2 MiniCssExtract plugin, the second addition is not needed, there should be a check, if it's already present in the plugins list then don't add it
Then to actually get the file to be output in the correct place it's missing a chunk definition
const mix = require('laravel-mix') const Chunks = require('laravel-mix/src/Chunks').Chunks; const File = require('laravel-mix/src/File'); const getPath = require('./src/getPath') const resolveOptions = require('./src/resolveOptions') const path = require('path') class Vuetify { constructor() { this.vuetifyPath = getPath('node_modules/vuetify') } withVuetifyLoader() { return this.vuetifyLoader === 'vuetify-loader' } withExtract() { return !!this.extract } withPostcss() { return !!this.postcss } register(loader, ...options) { this.vuetifyLoader = loader this.resolve(options) } resolve(options) { const resolved = resolveOptions(options) this.vuetifyLoaderOptions = resolved.vuetifyLoaderOptions this.sassArray = resolved.sassArray this.extract = resolved.extract this.postcss = resolved.postcss } dependencies() { this.requiresReload = true const deps = ['vuetify', 'sass', 'sass-loader', 'deepmerge'] if (this.withVuetifyLoader()) deps.push('vuetify-loader@next') if (this.withExtract()) deps.push('mini-css-extract-plugin') if (this.withPostcss()) deps.push('postcss-loader') return deps } generateRules() { return this.sassArray.map((t) => ({ test: t.sass, include: [this.vuetifyPath], use: [ this.withExtract() ? require('mini-css-extract-plugin').loader : 'vue-style-loader', 'css-loader', ...this.addPostcssIfNeeded(), { loader: 'sass-loader', options: { additionalData: t.data, implementation: require('sass'), sassOptions: { indentedSyntax: true } } } ] })) } webpackRules() { return this.generateRules() } webpackConfig(config) { this.excludeVuetifyPath(config) if (this.withVuetifyLoader()) this.addVuetifyLoader(config) if (this.withExtract()) this.addExtract(config) } addPostcssIfNeeded() { return this.withPostcss() ? ['postcss-loader'] : [] } addVuetifyLoader(config) { const VuetifyLoaderPlugin = require('vuetify-loader/lib/plugin') config.plugins.push(new VuetifyLoaderPlugin(this.vuetifyLoaderOptions)) } updateChunks(config) { const chunks = Chunks.instance(); const re = /(?<!node_modules)[\\/]node_modules[\\/](vuetify[\\/])/i; const groups = config.optimization.splitChunks.cacheGroups; Object.keys(groups).forEach((k) => { if (typeof groups[k] === 'object') { if (groups[k].type === 'css/mini-extract' && groups[k].chunks === 'all') { const orig = groups[k].test; groups[k].test = (module, context) => { const name = module.nameForCondition(); return name && !re.test(name) && orig(module, context); }; } } }) const output = new File(this.extract); chunks.add( 'styles-vuetify', output.normalizedOutputPath(), [re, module => module.type === 'css/mini-extract'], { chunks: 'all', enforce: true, type: 'css/mini-extract' } ); } addExtract(config) { const MiniCssExtractPlugin = require('mini-css-extract-plugin') const plugin = config.plugins.find((p) => p instanceof MiniCssExtractPlugin); if (!plugin) { config.plugins.push( new MiniCssExtractPlugin({ filename: '[name].css', chunkFilename: '[name].css', ignoreOrder: true, }) ) } else { plugin.options.ignoreOrder = true; } this.updateChunks(config); } excludeVuetifyPath(config) { for (const i of this.sassArray) config.module.rules .find((r) => String(r.test) === String(i.sass)) .exclude.push(this.vuetifyPath) } } mix.extend('vuetify', new Vuetify())Just noting that if you also do
.extract('vuetify')you'll get the css injs/vendor.cssand not the path specified in the extract option
I can confirm this works - sadly one cannot use .extract(['vuetify'], 'js/vendor/vuetify.js') as then there won't be any css exported
I changed it to the following, extract is now just a simple boolean - and both, the CSS and JS are getting extracted correctly:
const mix = require('laravel-mix')
const Chunks = require('laravel-mix/src/Chunks').Chunks;
const File = require('laravel-mix/src/File');
const getPath = require('vuetifyjs-mix-extension/src/getPath')
const resolveOptions = require('vuetifyjs-mix-extension/src/resolveOptions')
const path = require('path')
class Vuetify {
constructor() {
this.vuetifyPath = getPath('node_modules/vuetify')
}
withVuetifyLoader() {
return this.vuetifyLoader === 'vuetify-loader'
}
withExtract() {
return !!this.extract
}
withPostcss() {
return !!this.postcss
}
register(loader, ...options) {
this.vuetifyLoader = loader
this.resolve(options)
}
resolve(options) {
const resolved = resolveOptions(options)
this.vuetifyLoaderOptions = resolved.vuetifyLoaderOptions
this.sassArray = resolved.sassArray
this.extract = resolved.extract
this.postcss = resolved.postcss
}
dependencies() {
this.requiresReload = true
const deps = ['vuetify', 'sass', 'sass-loader', 'deepmerge']
if (this.withVuetifyLoader()) deps.push('vuetify-loader@next')
if (this.withExtract()) deps.push('mini-css-extract-plugin')
if (this.withPostcss()) deps.push('postcss-loader')
return deps
}
generateRules() {
return this.sassArray.map((t) => ({
test: t.sass,
include: [this.vuetifyPath],
use: [
this.withExtract()
? require('mini-css-extract-plugin').loader
: 'vue-style-loader',
'css-loader',
...this.addPostcssIfNeeded(),
{
loader: 'sass-loader',
options: {
additionalData: t.data,
implementation: require('sass'),
sassOptions: {
indentedSyntax: true
}
}
}
]
}))
}
webpackRules() {
return this.generateRules()
}
webpackConfig(config) {
this.excludeVuetifyPath(config)
if (this.withVuetifyLoader()) this.addVuetifyLoader(config)
if (this.withExtract()) this.addExtract(config)
}
addPostcssIfNeeded() {
return this.withPostcss() ? ['postcss-loader'] : []
}
addVuetifyLoader(config) {
const VuetifyLoaderPlugin = require('vuetify-loader/lib/plugin')
config.plugins.push(new VuetifyLoaderPlugin(this.vuetifyLoaderOptions))
}
updateChunks(config) {
if (!this.withExtract()) {
return;
}
const chunks = Chunks.instance();
const re = /(?<!node_modules)[\\/]node_modules[\\/](vuetify[\\/])/i;
const groups = config.optimization.splitChunks.cacheGroups;
Object.keys(groups).forEach((k) => {
if (typeof groups[k] === 'object') {
if (groups[k].type === 'css/mini-extract' && groups[k].chunks === 'all') {
const orig = groups[k].test;
groups[k].test = (module, context) => {
const name = module.nameForCondition();
return name && !re.test(name) && orig(module, context);
};
}
}
});
chunks.add(
'styles-vuetify',
new File('css/vendors/vuetify.css').normalizedOutputPath(),
[re, module => module.type === 'css/mini-extract'],
{
chunks: 'all',
enforce: true,
type: 'css/mini-extract'
}
);
chunks.add(
'script-vuetify',
new File('js/vendors/vuetify.js').normalizedOutputPath(),
[re, module => module.type === 'javascript/auto'],
{
chunks: 'all',
enforce: true,
type: 'javascript/auto'
}
);
}
addExtract(config) {
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const plugin = config.plugins.find((p) => p instanceof MiniCssExtractPlugin);
if (!plugin) {
config.plugins.push(
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[name].css',
ignoreOrder: true,
})
)
} else {
plugin.options.ignoreOrder = true;
}
this.updateChunks(config);
}
excludeVuetifyPath(config) {
for (const i of this.sassArray)
config.module.rules
.find((r) => String(r.test) === String(i.sass))
.exclude.push(this.vuetifyPath)
}
}
mix.extend('vuetify', new Vuetify())