Skip to content

Commit 6466340

Browse files
aarbimikkpokkthecrypticace
authored
Extract CSS for a react project (laravel-mix#2939)
Co-authored-by: Mikk Pokk <[email protected]> Co-authored-by: Jordan Pittman <[email protected]>
1 parent 2ae0ee6 commit 6466340

17 files changed

+293
-59
lines changed

docs/examples.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,13 @@ mix.js('src/app.js', 'js')
7979
.react();
8080
```
8181

82+
### Extract React Component CSS to its Own File when in Production build
83+
84+
```js
85+
mix.js('src/app.js', 'js')
86+
.react({ extractStyles: mix.inProduction() });
87+
```
88+
8289
### Compile JavaScript and Extract Lodash to its Own File
8390

8491
```js

src/components/Combine.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,7 @@ class Combine {
5353
src: this.src,
5454
output: this.output,
5555
babel: this.babel,
56-
ignore: [
57-
this.output.relativePath(),
58-
],
56+
ignore: [this.output.relativePath()]
5957
})
6058
);
6159
}

src/components/CssWebpackConfig.js

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class CssWebpackConfig extends AutomaticComponent {
3232
type: 'scss',
3333
test: /\.scss$/,
3434
loader: {
35-
loader: Mix.resolve('sass-loader'),
35+
loader: this.context.resolve('sass-loader'),
3636
options: {
3737
sassOptions: {
3838
precision: 8,
@@ -46,7 +46,7 @@ class CssWebpackConfig extends AutomaticComponent {
4646
type: 'sass',
4747
test: /\.sass$/,
4848
loader: {
49-
loader: Mix.resolve('sass-loader'),
49+
loader: this.context.resolve('sass-loader'),
5050
options: {
5151
sassOptions: {
5252
precision: 8,
@@ -60,22 +60,22 @@ class CssWebpackConfig extends AutomaticComponent {
6060
command: 'less',
6161
type: 'less',
6262
test: /\.less$/,
63-
loader: { loader: Mix.resolve('less-loader') }
63+
loader: { loader: this.context.resolve('less-loader') }
6464
},
6565
{
6666
command: 'stylus',
6767
type: 'stylus',
6868
test: /\.styl(us)?$/,
69-
loader: { loader: Mix.resolve('stylus-loader') }
69+
loader: { loader: this.context.resolve('stylus-loader') }
7070
}
7171
].map(rule => this.createRule(rule));
7272
}
7373

7474
/**
7575
* Build up the appropriate loaders for the given rule.
7676
*
77-
* @param {Object} rule
78-
* @returns {{test: *, use: *[], exclude: (*[]|[])}}
77+
* @param {import('webpack').RuleSetRule & {test: RegExp, command: string}} rule
78+
* @returns {import('webpack').RuleSetRule}
7979
*/
8080
createRule(rule) {
8181
return {
@@ -85,12 +85,22 @@ class CssWebpackConfig extends AutomaticComponent {
8585
{
8686
// Ex: foo.css?module
8787
resourceQuery: /module/,
88-
use: this.createLoaderList(rule, true)
88+
use: this.createLoaderList(rule, {
89+
mode: 'local',
90+
auto: undefined,
91+
localIdentName:
92+
this.context.config.cssModuleIdentifier || '[hash:base64]'
93+
})
8994
},
9095
{
9196
// Ex: foo.css
9297
// Ex: foo.module.css
93-
use: this.createLoaderList(rule, { auto: true })
98+
use: this.createLoaderList(rule, {
99+
mode: 'local',
100+
auto: true,
101+
localIdentName:
102+
this.context.config.cssModuleIdentifier || '[hash:base64]'
103+
})
94104
}
95105
]
96106
};
@@ -100,14 +110,14 @@ class CssWebpackConfig extends AutomaticComponent {
100110
* Build up the appropriate loaders for the given rule.
101111
*
102112
* @param {Object} rule
103-
* @param {string|boolean} useCssModules
113+
* @param {object} useCssModules
104114
* @returns {any[]}
105115
*/
106116
createLoaderList(rule, useCssModules) {
107117
return [
108118
...CssWebpackConfig.afterLoaders(),
109119
{
110-
loader: Mix.resolve('css-loader'),
120+
loader: this.context.resolve('css-loader'),
111121
options: {
112122
url: (url, resourcePath) => {
113123
if (url.startsWith('/')) {
@@ -120,7 +130,7 @@ class CssWebpackConfig extends AutomaticComponent {
120130
}
121131
},
122132
{
123-
loader: Mix.resolve('postcss-loader'),
133+
loader: this.context.resolve('postcss-loader'),
124134
options: {
125135
postcssOptions: {
126136
plugins: new PostCssPluginsFactory({}, Config).load(),
@@ -142,7 +152,7 @@ class CssWebpackConfig extends AutomaticComponent {
142152
* @param {string} command
143153
*/
144154
excludePathsFor(command) {
145-
let exclusions = Mix.components.get(command);
155+
let exclusions = this.context.components.get(command);
146156

147157
if (command === 'css' || !exclusions) {
148158
return [];
@@ -210,6 +220,7 @@ class CssWebpackConfig extends AutomaticComponent {
210220
/** @private */
211221
static get wantsVueStyleLoader() {
212222
const VueFeature = Mix.components.get('vue');
223+
213224
return VueFeature && VueFeature.options && VueFeature.options.useVueStyleLoader;
214225
}
215226

@@ -258,6 +269,10 @@ class CssWebpackConfig extends AutomaticComponent {
258269
return Array.wrap(files).map(file => Mix.paths.root(file));
259270
});
260271
}
272+
273+
get context() {
274+
return global.Mix;
275+
}
261276
}
262277

263278
module.exports = CssWebpackConfig;

src/components/React.js

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
const semver = require('semver');
2+
const File = require('../File');
23

34
class React {
5+
/** @type {import('laravel-mix').ReactConfig} */
6+
options = {
7+
extractStyles: false
8+
};
9+
410
/**
511
* Required dependencies for the component.
612
*/
@@ -26,8 +32,10 @@ class React {
2632

2733
/**
2834
* Register the component.
35+
*
36+
* @param {import('laravel-mix').ReactConfig} options
2937
*/
30-
register() {
38+
register(options = {}) {
3139
if (
3240
arguments.length === 2 &&
3341
typeof arguments[0] === 'string' &&
@@ -37,6 +45,11 @@ class React {
3745
'mix.react() is now a feature flag. Use mix.js(source, destination).react() instead'
3846
);
3947
}
48+
49+
this.options = Object.assign(this.options, options);
50+
51+
this.context.extractingStyles =
52+
this.context.extractingStyles || !!this.options.extractStyles;
4053
}
4154

4255
/**
@@ -79,6 +92,94 @@ class React {
7992
library() {
8093
return require('react');
8194
}
95+
96+
/**
97+
* Update CSS chunks to extract React styles
98+
*/
99+
updateChunks() {
100+
if (this.options.extractStyles === false) {
101+
return;
102+
}
103+
104+
this.context.chunks.add(
105+
'styles-js',
106+
this.styleChunkName(),
107+
[/.(j|t)s$/, module => module.type === 'css/mini-extract'],
108+
{
109+
chunks: 'all',
110+
enforce: true,
111+
type: 'css/mini-extract'
112+
}
113+
);
114+
115+
this.context.chunks.add(
116+
'styles-jsx',
117+
this.styleChunkName(),
118+
[/.(j|t)sx?$/, module => module.type === 'css/mini-extract'],
119+
{
120+
chunks: 'all',
121+
enforce: true,
122+
type: 'css/mini-extract'
123+
}
124+
);
125+
}
126+
127+
/**
128+
* Override the generated webpack configuration.
129+
*
130+
* @param {Object} config
131+
*/
132+
webpackConfig(config) {
133+
this.updateChunks();
134+
135+
return config;
136+
}
137+
138+
/**
139+
* Get the name of the style chunk.
140+
*
141+
* @returns {string}
142+
*/
143+
styleChunkName() {
144+
// If the user set extractStyles: true, we'll try
145+
// to append the React styles to an existing CSS chunk.
146+
if (this.options.extractStyles === true) {
147+
let chunk = this.context.chunks.find((chunk, id) => id.startsWith('styles-'));
148+
149+
if (chunk) {
150+
return chunk.name;
151+
}
152+
}
153+
154+
return this.extractFile().relativePathWithoutExtension();
155+
}
156+
157+
/**
158+
* Get a new File instance for the extracted file.
159+
*
160+
* @returns {File}
161+
*/
162+
extractFile() {
163+
return new File(this.extractFileName());
164+
}
165+
166+
/**
167+
* Determine the extract file name.
168+
*
169+
* @return {string}
170+
*/
171+
extractFileName() {
172+
let fileName =
173+
typeof this.options.extractStyles === 'string'
174+
? this.options.extractStyles
175+
: '/css/react-styles.css';
176+
177+
return fileName.replace(Config.publicPath, '').replace(/^\//, '');
178+
}
179+
180+
get context() {
181+
return global.Mix;
182+
}
82183
}
83184

84185
module.exports = React;

src/components/Vue.js

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
1-
let { Chunks } = require('../Chunks');
21
let File = require('../File');
32
let VueVersion = require('../VueVersion');
43
let AppendVueStylesPlugin = require('../webpackPlugins/Css/AppendVueStylesPlugin');
54

65
/** @typedef {import("vue").VueLoaderOptions} VueLoaderOptions */
76

87
class Vue {
9-
/**
10-
* Create a new component instance.
11-
*/
12-
constructor() {
13-
this.chunks = Chunks.instance();
14-
}
15-
168
/**
179
* Register the component.
1810
*
@@ -34,7 +26,7 @@ class Vue {
3426
);
3527
}
3628

37-
this.version = new VueVersion(this._mix).detect(options.version);
29+
this.version = new VueVersion(this.context).detect(options.version);
3830

3931
this.options = Object.assign(
4032
{
@@ -46,8 +38,9 @@ class Vue {
4638
options
4739
);
4840

49-
Mix.globalStyles = this.options.globalStyles;
50-
Mix.extractingStyles = !!this.options.extractStyles;
41+
this.context.globalStyles = this.options.globalStyles;
42+
this.context.extractingStyles =
43+
this.context.extractingStyles || !!this.options.extractStyles;
5144

5245
this.addDefines();
5346
}
@@ -81,7 +74,7 @@ class Vue {
8174
test: /\.vue$/,
8275
use: [
8376
{
84-
loader: this._mix.resolve('vue-loader'),
77+
loader: this.context.resolve('vue-loader'),
8578
options: this.options.options || Config.vue || {}
8679
}
8780
]
@@ -115,7 +108,7 @@ class Vue {
115108
* webpack plugins to be appended to the master config.
116109
*/
117110
webpackPlugins() {
118-
let { VueLoaderPlugin } = require(this._mix.resolve('vue-loader'));
111+
let { VueLoaderPlugin } = require(this.context.resolve('vue-loader'));
119112

120113
return [new VueLoaderPlugin(), new AppendVueStylesPlugin()];
121114
}
@@ -128,7 +121,7 @@ class Vue {
128121
return;
129122
}
130123

131-
this.chunks.add(
124+
this.context.chunks.add(
132125
'styles-vue',
133126
this.styleChunkName(),
134127
[/.vue$/, module => module.type === 'css/mini-extract'],
@@ -139,7 +132,7 @@ class Vue {
139132
}
140133
);
141134

142-
this.chunks.add(
135+
this.context.chunks.add(
143136
'styles-jsx',
144137
this.styleChunkName(),
145138
[/.jsx$/, module => module.type === 'css/mini-extract'],
@@ -160,7 +153,7 @@ class Vue {
160153
// If the user set extractStyles: true, we'll try
161154
// to append the Vue styles to an existing CSS chunk.
162155
if (this.options.extractStyles === true) {
163-
let chunk = this.chunks.find((chunk, id) => id.startsWith('styles-'));
156+
let chunk = this.context.chunks.find((chunk, id) => id.startsWith('styles-'));
164157

165158
if (chunk) {
166159
return chunk.name;
@@ -203,18 +196,16 @@ class Vue {
203196
return;
204197
}
205198

206-
this._mix.api.define({
199+
this.context.api.define({
207200
__VUE_OPTIONS_API__: 'true',
208201
__VUE_PROD_DEVTOOLS__: 'false'
209202
});
210203
}
211204

212205
/**
213206
* @internal
214-
* @returns {import("../Mix")}
215207
**/
216-
get _mix() {
217-
// @ts-ignore
208+
get context() {
218209
return global.Mix;
219210
}
220211
}

0 commit comments

Comments
 (0)