By default, fresh Laravel installation comes with Vue scaffolding which provides you with a quick starting point for writing your Vue components and easily compiles them into a single browser ready JavaScript file. It really helps Vue developers to QuickStart their project. But, since this article is all about using React for your frontend, artisan preset command comes in handy to replace default Vue scaffolding with React.
On any fresh Laravel application run
php artisan preset react
After running this command, Laravel will ask you to run
npm install && npm run dev
This will resolve all the dependencies required to transpile your JSX code into javascript code. Now, as we all know React doesn’t require us to follow a specific way to structure our projects but a common way is to locate CSS, JS, and tests together inside folders grouped by feature.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
common/ DisplayPicture.js DisplayPicture.css NewsFeed/ NewsFeed.js NewsFeed.css FeedStory.js Config/ index.js Config.js UserConfig.js AccountConfig.css NewsFeedConfig.js |
But how Laravel structure your Javascript and CSS code is quite different from React structure. Laravel has its own resources folder where you put your Javascript and CSS code into separate folders and specify those paths in Laravel webpack.mix.js file.
1 2 3 4 5 6 |
resources/ assets/ js/ App.js sass/ app.scss |
1 2 3 4 |
let mix = require('laravel-mix'); mix.js('resources/assets/js/app.js', 'public/js') .sass('resources/assets/sass/app.scss', 'public/css'); |
This configuration is fine if you’re writing your Javascript and CSS separately and don’t require stylesheets directly in your React components. But, since we’re requiring our CSS styles directly into our components this configuration will fail. When working with Vue scaffolding, there’s an option for extracting CSS styles into dedicated CSS files. All you have to add is
1 |
mix.options({ extractVueStyles: true }); |
to your webpack.config.js file. This method doesn’t work for React. We have to use extract-text-webpack-plugin to extract CSS manually. You can install it by npm.
npm install --save-dev extract-text-webpack-plugin
Now you need to modify your webpack.config.js file to use this plugin. Your config file will look something like this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
const { mix } = require('laravel-mix'); const path = require('path'); const ExtractTextPlugin = require("extract-text-webpack-plugin"); mix.react('resources/assets/src/app.js', 'public/assets/bundle'); mix.webpackConfig({ module: { rules: [ { test: /\.s[ac]ss$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use : [ { loader : 'css-loader', }, { loader : 'postcss-loader', }, { loader : 'sass-loader', } ] }) } ] }, plugins: [ new ExtractTextPlugin('[name].[contenthash].css') ], devtool : 'source-map' }).sourceMaps(); mix.options({ processCssUrls: false }); |
Mix provides a useful webpackConfig
method that allows you to merge any short Webpack configuration overrides. Now if you require SASS/SCSS files in your React components, webpack will run preproccessor loaders on them to transpile them into a dedicated browser ready CSS file.
Update: Important Note
If your using laravel-mix 0.12 with webpack 2.0.* then you’ll be able to successfully build your code. If you’re running Laravel 5.6, it ships with laravel-mix ^2.0 and webpack 3.11.0 above configuration will fail. You’ll get an error saying
Invalid CSS after “…load the styles”: expected 1 selector or at-rule, was “var content = requi”
I spent hours trying to debug this error and found that behind the scenes mix uses a package webpack-merge to merge custom rules and loaders with webpack config. In node_modules\laravel-mix\src\builder\WebpackConfig.js file there’s this method mergeCustomConfig(
1 2 3 4 5 6 7 8 9 10 11 12 |
mergeCustomConfig() { if (Config.webpackConfig) { this.webpackConfig = require('webpack-merge').smart( this.webpackConfig, Config.webpackConfig ); const util = require('util'); console.log(util.inspect(this.webpackConfig, false, null)) } } |
Now mix already has a rule for testing /\.s[ac]ss$/ files. If your passed object configuration keys don’t match the predefined config, it will not merge it, instead end up adding a duplicate rule for SASS files.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
{ test: /\.s[ac]ss$/, exclude: [], loaders: ['style-loader', 'css-loader', 'sass-loader'] }, { test: /\.s[ac]ss$/, loader: [{ loader: 'C:\\xampp\\htdocs\\laravel-react\\node_modules\\extract-text-webpack-plugin\\dist\\loader.js', options: { omit: 1, remove: true } }, { loader: 'style-loader' }, { loader: 'css-loader' }, { loader: 'sass-loader' } ] } |
Now if you’re running an older laravel-mix version, you can use above mentioned configuration file. For newer version, you need to add exclude : [] key to rules array.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
mix.webpackConfig({ module: { rules: [ { test: /\.s[ac]ss$/, exclude : [], // For newer versions loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use : [ { loader : 'css-loader', }, { loader : 'sass-loader', } ] }) } ] }, plugins: [ new ExtractTextPlugin('[name].css') ], devtool : 'source-map' }); |
By adding an exclude key to rules object array, webpack-merge was able to merge it with the existing configuration and code build was successfull.
Hi, first off, thanks for your greate post. Anyway, I got a question, why does adding exclude : [] key to rules array fix the problem? I can’t figure it out 🙁