Skip to content

Commit 6025974

Browse files
committed
Improve mix.combine() options - resolves laravel-mix#2465
1 parent 1ef4142 commit 6025974

File tree

8 files changed

+203
-87
lines changed

8 files changed

+203
-87
lines changed

docs/concatenation-and-minification.md

Lines changed: 63 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,90 @@
11
# Concatenation and Minification
22

3+
- [Basic Usage](#basic-usage)
4+
- [Concatenate All Files in a Directory](#concatenate-all-files-in-a-directory)
5+
- [Concatenate All Matching Files in a Directory](#concatenate-all-matching-files-in-a-directory)
6+
- [Concatenate an Array of Files](#concatenate-an-array-of-files)
7+
- [Concatenate Scripts and Apply Babel Compilation](#concatenate-scripts-and-apply-babel-compilation)
8+
- [File Minification](#file-minification)
9+
- [Minify a Single File](#minify-a-single-file)
10+
- [Minify an Array of File](#minify-an-array-of-file)
11+
12+
Laravel Mix and webpack should take care of all the necessary module bundling and minification for you. However, you may have lingering legacy code or vendor libraries that need to remain separate from your core webpack bundle. Not a problem.
13+
14+
For basic file concatenation and minification, Mix has you covered.
15+
16+
### Basic Usage
17+
18+
Consider the following Mix configuration file.
19+
320
```js
4-
mix.combine(['src', 'files'], 'destination');
5-
mix.babel(['src', 'files'], destination);
6-
mix.minify('src');
7-
mix.minify(['src']);
21+
mix.combine(['one.js', 'two.js'], 'merged.js');
822
```
923

10-
If used properly, Laravel Mix and webpack should take care of all the necessary module bundling and minification for you. However, you may have some legacy code or vendor libraries that need to be concatenated and minified. Not a problem.
24+
This instructs Mix to merge - or concatenate - `one.js` and `two.js` into a single file, called `merged.js`. As always, during development, that merged file will remain uncompressed. However, when building for production, `merged.js` will of course be minified.
1125

12-
### Combine Files
26+
> If it reads better to you, `mix.scripts()` and `mix.styles()` are aliases for `mix.combine()`.
1327
14-
Consider the following snippet:
28+
#### Concatenate All Files in a Directory
1529

1630
```js
17-
mix.combine(['one.js', 'two.js'], 'merged.js');
31+
mix.combine('path/to/dir', 'all-files.js');
32+
33+
// or:
34+
35+
mix.scripts('path/to/dir', 'all-files.js');
36+
```
37+
38+
#### Concatenate All Matching Files in a Directory
39+
40+
```js
41+
mix.combine('path/to/dir/*.css', 'all-files.css');
42+
43+
// or:
44+
45+
mix.styles('path/to/dir/*.css', 'all-files.css');
1846
```
1947

20-
This will naturally merge `one.js` and `two.js` into a single file, called `merged.js`. As always, during development, that merged file will remain uncompressed. However, for production \(`export NODE_ENV=production`\), this command will additionally minify `merged.js`.
48+
#### Concatenate an Array of Files
2149

22-
#### Combine Files With Babel Compilation
50+
```js
51+
mix.combine([
52+
'path/to/dir/*.css',
53+
'path/to/second/dir/*.css'
54+
], 'all-files.css');
55+
```
2356

24-
If you need to concatenate JavaScript files that have been written in ES2015, you may update your `mix.combine()` call to `mix.babel()`. The method signature is identical. The only difference is that, after the files have been concatenated, Laravel Mix will perform Babel compilation on the result to transform the code to vanilla JavaScript that all browsers can understand.
57+
#### Concatenate Scripts and Apply Babel Compilation
58+
59+
If you need to concatenate and then compile JavaScript files that have been written with the latest JavaScript syntax, you may either set the third argument of `mix.combine()` to `true`, or instead call `mix.babel()`. Other than the Babel compilation, both commands are identical.
2560

2661
```js
62+
// Both of these are identical.
63+
mix.combine(['one.js', 'two.js'], 'merged.js', true);
2764
mix.babel(['one.js', 'two.js'], 'merged.js');
2865
```
2966

30-
### Minify Files
67+
### File Minification
3168

3269
Similarly, you may also minify one or more files with the `mix.minify()` command.
3370

71+
There are a few things worth noting here:
72+
73+
1. `mix.minify()` will create a companion `*.min.{extension}` file. So minifying `app.js` will generate `app.min.js` within the same directory.
74+
2. As always, minification will only take place during a production build.
75+
3. There is no need to call `mix.combine(['one.js', 'two.js'], 'merged.js').minify('merged.js');`Just stick with the single `mix.combine()` call. It'll take care of both.
76+
77+
> **Important**: Minification is only available for CSS and JavaScript files. The minifier will not understand any other provided file type.
78+
79+
#### Minify a Single File
80+
3481
```js
3582
mix.minify('path/to/file.js');
36-
mix.minify(['this/one.js', 'and/this/one.js']);
3783
```
3884

39-
There are a few things worth noting here:
85+
#### Minify an Array of File
4086

41-
1. This method will create a companion `*.min.ext` file. So minifying `app.js` will generate `app.min.js`.
42-
2. Once again, the minification will only take place during a production build. \(`export NODE_ENV=production`\).
43-
3. There is no need to call `mix.combine(['one.js', 'two.js'], 'merged.js').minify('merged.js');`Just stick with the single `mix.combine()` call. It'll take care of both.
87+
```js
88+
mix.minify(['this/one.js', 'and/this/one.js']);
89+
```
4490

45-
> **Important**: Please note that minification is only available for CSS and JavaScript files. The minifier will not understand any other provided file type.

src/Assert.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,15 @@ class Assert {
4848
* @param {File} output
4949
*/
5050
static combine(src, output) {
51+
assert(
52+
typeof src === 'string' || Array.isArray(src),
53+
`mix.combine() requires a valid src string or array.`
54+
);
55+
5156
assert(
5257
output.isFile(),
53-
'mix.combine() requires a full output file path as the second argument.'
58+
'mix.combine() requires a full output file path as the second argument. Got ' +
59+
output.path()
5460
);
5561
}
5662

src/components/Combine.js

Lines changed: 54 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
let ConcatFilesTask = require('../tasks/ConcatenateFilesTask');
2-
let Assert = require('../Assert');
3-
let File = require('../File');
41
let path = require('path');
5-
let collect = require('collect.js');
62
let glob = require('glob');
3+
let File = require('../File');
4+
let Assert = require('../Assert');
5+
let { collect } = require('collect.js');
6+
let ConcatFilesTask = require('../tasks/ConcatenateFilesTask');
77

88
class Combine {
99
/**
@@ -14,41 +14,71 @@ class Combine {
1414
}
1515

1616
/**
17-
*
1817
* Register the component.
1918
*
20-
* @param {*} src
21-
* @param {string} output
19+
* @param {String|Array} src
20+
* @param {String} output
2221
* @param {Boolean} babel
2322
*/
2423
register(src, output = '', babel = false) {
25-
if (this.caller === 'babel') {
26-
babel = true;
27-
}
24+
this.src = src;
25+
this.output = output;
26+
this.babel = babel || this.caller === 'babel';
2827

2928
if (this.caller === 'minify') {
30-
if (Array.isArray(src)) {
31-
src.forEach(file => this.register(file));
29+
this.registerMinify();
30+
}
3231

33-
return this;
34-
}
32+
this.addTask();
33+
}
3534

36-
output = src.replace(/\.([a-z]{2,})$/i, '.min.$1');
35+
/**
36+
* Register the minify task.
37+
*/
38+
registerMinify() {
39+
if (Array.isArray(this.src)) {
40+
this.src.forEach(file => this.register(file));
3741
}
3842

39-
output = new File(output);
43+
this.output = this.src.replace(/\.([a-z]{2,})$/i, '.min.$1');
44+
}
4045

41-
Assert.combine(src, output);
46+
/**
47+
* Add a new ConcatFiles task.
48+
*/
49+
addTask() {
50+
this.output = new File(this.output);
4251

43-
if (typeof src === 'string' && File.find(src).isDirectory()) {
44-
src = collect(glob.sync(path.join(src, '**/*'), { nodir: true }))
45-
.except(output.relativePath())
46-
.all();
47-
}
52+
Assert.combine(this.src, this.output);
4853

49-
let task = new ConcatFilesTask({ src, output, babel });
54+
Mix.addTask(
55+
new ConcatFilesTask({
56+
src: collect(this.glob(this.src))
57+
.except(this.output.relativePath())
58+
.all(),
59+
output: this.output,
60+
babel: this.babel
61+
})
62+
);
63+
}
5064

51-
Mix.addTask(task);
65+
/**
66+
* Find all relevant files matching the given source path.
67+
*
68+
* @param {String|Array} src
69+
* @returns {Array}
70+
*/
71+
glob(src) {
72+
return collect([].concat(src))
73+
.flatMap(srcPath =>
74+
glob.sync(
75+
File.find(srcPath).isDirectory()
76+
? path.join(srcPath, '**/*')
77+
: srcPath,
78+
{ nodir: true }
79+
)
80+
)
81+
.all();
5282
}
5383
}
5484

test/features/combine.js

Lines changed: 75 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,54 @@
11
import test from 'ava';
2-
import eol from 'eol';
3-
import fs from 'fs-extra';
42
import File from '../../src/File';
53
import assert from '../helpers/assertions';
64
import webpack from '../helpers/webpack';
75

86
import '../helpers/mix';
97

10-
test('it combines a folder of scripts', async t => {
11-
let output = `test/fixtures/app/dist/all.js`;
12-
13-
mix.scripts(`test/fixtures/app/src/js`, output);
8+
test('it accepts a src directory', async t => {
9+
mix.scripts(
10+
'test/fixtures/app/src/combine/foo',
11+
'test/fixtures/app/dist/js/combined-scripts.js'
12+
);
1413

1514
await webpack.compile();
1615

17-
t.true(File.exists(output));
18-
19-
t.is(
20-
"alert('another stub');\n\nalert('stub');\n",
21-
File.find(output).read()
16+
assert.fileMatchesCss(
17+
'test/fixtures/app/dist/js/combined-scripts.js',
18+
"alert('foo1');alert('foo2');",
19+
t
2220
);
2321
});
2422

25-
test('it can minify a file', async t => {
26-
mix.js(`test/fixtures/app/src/js/app.js`, 'js').minify(
27-
`test/fixtures/app/dist/js/app.js`
23+
test('it accepts a src wildcard', async t => {
24+
mix.scripts(
25+
'test/fixtures/app/src/combine/foo/*.js',
26+
'test/fixtures/app/dist/js/combined-scripts.js'
2827
);
2928

3029
await webpack.compile();
3130

32-
t.true(File.exists(`test/fixtures/app/dist/js/app.min.js`));
31+
assert.fileMatchesCss(
32+
'test/fixtures/app/dist/js/combined-scripts.js',
33+
"alert('foo1');alert('foo2');",
34+
t
35+
);
36+
});
3337

34-
assert.manifestEquals(
35-
{
36-
'/js/app.js': '/js/app.js',
37-
'/js/app.min.js': '/js/app.min.js'
38-
},
38+
test('it accepts a src array of wildcards', async t => {
39+
mix.scripts(
40+
[
41+
'test/fixtures/app/src/combine/foo/*.js',
42+
`test/fixtures/app/src/combine/bar/*.js`
43+
],
44+
'test/fixtures/app/dist/js/combined-scripts.js'
45+
);
46+
47+
await webpack.compile();
48+
49+
assert.fileMatchesCss(
50+
'test/fixtures/app/dist/js/combined-scripts.js',
51+
"alert('foo1');alert('foo2');alert('bar1');alert('bar2');",
3952
t
4053
);
4154
});
@@ -68,17 +81,10 @@ test('it compiles JS and then combines the bundles files.', async t => {
6881
test('it concatenates a directory of files, copies the output to a new location, and then minifies it in production mode', async t => {
6982
Config.production = true;
7083

71-
// Setup
72-
new File('test/fixtures/app/src/combine/one.js')
73-
.makeDirectories()
74-
.write("alert('one')");
75-
76-
new File('test/fixtures/app/src/combine/two.js').write("alert('two')");
77-
7884
mix.scripts(
7985
[
80-
`test/fixtures/app/src/combine/one.js`,
81-
`test/fixtures/app/src/combine/two.js`
86+
`test/fixtures/app/src/combine/foo/one.js`,
87+
`test/fixtures/app/src/combine/foo/two.js`
8288
],
8389
'test/fixtures/app/dist/output/combined-scripts.js'
8490
);
@@ -90,28 +96,53 @@ test('it concatenates a directory of files, copies the output to a new location,
9096

9197
await webpack.compile();
9298

93-
let expected = 'alert("one"),alert("two");' + eol.auto;
99+
assert.fileMatchesCss(
100+
'test/fixtures/app/dist/js/combined-scripts.js',
101+
'alert("foo1"),alert("foo2");',
102+
t
103+
);
104+
});
94105

95-
t.is(
96-
expected,
97-
File.find('test/fixtures/app/dist/js/combined-scripts.js').read()
106+
test('it minifies a file', async t => {
107+
mix.js(`test/fixtures/app/src/js/app.js`, 'js').minify(
108+
`test/fixtures/app/dist/js/app.js`
98109
);
99110

100-
// Clean up
101-
fs.removeSync('test/fixtures/app/src/combine');
111+
await webpack.compile();
112+
113+
t.true(File.exists(`test/fixtures/app/dist/js/app.min.js`));
114+
115+
assert.manifestEquals(
116+
{
117+
'/js/app.js': '/js/app.js',
118+
'/js/app.min.js': '/js/app.min.js'
119+
},
120+
t
121+
);
102122
});
103123

104-
test('mix.combine/scripts/styles/babel()', t => {
105-
t.deepEqual(mix, mix.combine([], 'dist/js/combined.js'));
124+
test('it minifies an array of files', async t => {
125+
mix.minify([
126+
`test/fixtures/app/src/combine/foo/one.js`,
127+
`test/fixtures/app/src/combine/foo/two.js`
128+
]);
106129

107-
t.is(1, Mix.tasks.length);
130+
await webpack.compile();
108131

109-
t.deepEqual(mix, mix.scripts([], 'dist/js/combined.js'));
110-
t.deepEqual(mix, mix.babel([], 'dist/js/combined.js'));
111-
});
132+
t.true(File.exists(`test/fixtures/app/src/combine/foo/one.min.js`));
133+
t.true(File.exists(`test/fixtures/app/src/combine/foo/two.min.js`));
112134

113-
test('mix.minify()', t => {
114-
t.deepEqual(mix, mix.minify('dist/js/minify.js'));
135+
assert.manifestEquals(
136+
{
137+
'/test/fixtures/app/src/combine/foo/one.min.js':
138+
'/test/fixtures/app/src/combine/foo/one.min.js',
139+
'/test/fixtures/app/src/combine/foo/two.min.js':
140+
'/test/fixtures/app/src/combine/foo/two.min.js'
141+
},
142+
t
143+
);
115144

116-
t.is(1, Mix.tasks.length);
145+
// Clean Up
146+
File.find(`test/fixtures/app/src/combine/foo/one.min.js`).delete();
147+
File.find(`test/fixtures/app/src/combine/foo/two.min.js`).delete();
117148
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
alert('bar1');
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
alert('bar2');

0 commit comments

Comments
 (0)