Laravel 文档阅读:用 Laravel Mix 编译资产

翻译、衍生自:https://laravel.com/docs/5.5/mix

简介

这里的编译的「资产」可以简单理解为项目中的样式表文件和 JavaScript 脚本文件。

Laravel Mix 是 Laravel 提供的前端脚本构建工具,能够用几种常用的 CSS 和 JavaScript 预处理器处理项目脚本。 它构建在 Webpack 之上,隐藏了 Webpack 底层的复杂配置,暴露出简单易用的 API,让你轻松编译前端脚本不是梦!

下面是一个例子,流畅的链式方法调用,对 JavaScript 和 Sass 这两种不同资产类型做管道处理,在一条语句内完成,非常简单:

mix.js('resources/assets/js/app.js', 'public/js')
   .sass('resources/assets/sass/app.scss', 'public/css');

如果你以前用过 Webpack,就知道它的配置是多么的复杂了,因此当你第一眼看见 Laravel Mix 的时候,你就会情不自禁的爱上她。你也并非一定要用 Laravel Mix 开发,可以选择任何你希望使用的构建工具(像 Gulp),甚至不用任何构建工具都可以。

安装 & 设置

安装 Node

开始用 Mix 前,必须要保证在你的机器上安装了 Node.js 和 NPM 哦。

node -v
npm -v

如果你是用 Homestead 的话,就可以省略这一步了,因为 Homestead 中包含开发 Laravel 程序所需要的一切环境。

Laravel Mix

对,没有错,这一步就可以直接安装 Laravel Mix 了。在你刚创建的一个 Laravel 项目里,根目录配置文件 package.json 中,已经为你预设好了。

"devDependencies": {
    "axios": "^0.16.2",
    "bootstrap-sass": "^3.3.7",
    "cross-env": "^5.0.1",
    "jquery": "^3.1.1",
    "laravel-mix": "^1.0",
    "lodash": "^4.17.4",
    "vue": "^2.1.10"
}

package.json 就像是 composer.json 文件。不过前者是安装 Node 依赖包使用的,后者是安装 PHP 依赖包使用的。好,下面开始安装:

npm install

如果你是用 Windows 系统开发的,或者是用了安装在 Windows 系统上的虚拟机(VM)。你就可能需要在执行 npm install 命令的时候带上 --no-bin-links 选项:

npm install --no-bin-links

运行 Mix

Mix 是构建在 Webpack 上的一个配置层。所以执行 Mix 任务时,需要使用 NPM 脚本命令,这些命令在 package.json 文件中定义:

// Run all Mix tasks...
npm run dev

// Run all Mix tasks and minify output...
npm run production

监听资产变化

npm run watch 与前两条命令不同的是,它会在你的终端一直运行、监听所有相关文件的改变,一旦有文件改变,就会自动重新编译:

npm run watch

在某些情况下,这条命令对于 Webpack 不好使,它不给你重新编译的,针对这种情况,考虑使用 watch-poll 命令:

npm run watch-poll

使用样式表

webpack.min.js 是所有资产编译的入口文件,可以把它看做是包裹了 Webpack 底层复杂配置的一个轻量的配置文件(同样是配置文件,差距就是那么大呢)。在这里面定义了链式的 Mix 任务,每个 Mix 任务就相当于是一个编译规则呢。

Less

less 方法用来把 Less 文件编译为 CSS。下面,让我们把原本的 app.less 文件输出为 public/css/app.css 文件。

mix.less('resources/assets/less/app.less', 'public/css');

你可以同时调用多个 less 方法来满足项目中编译不同地方的样式文件的需要:

mix.less('resources/assets/less/app.less', 'public/css')
   .less('resources/assets/less/admin.less', 'public/css');

如果要自定义编译出来的 CSS 文件名,那么在第二个参数里带上文件完整路径名就 OK 了:

mix.less('resources/assets/less/app.less', 'public/stylesheets/styles.css');

less 方法底层使用的是 Webpack 的 Less loader 插件,如果你要深度定制这个插件的配置信息,为 min.less() 传递第三个参数(对象类型)就 OK 了。

mix.less('resources/assets/less/app.less', 'public/css', {
    strictMath: true
});

Sass

sass 方法用来把 Sass 文件编译为 CSS。它像下面这样用:

min.sass('resources/assets/sass/app.scss', 'public/css');

哎哟,不错哦,你有没有感觉跟 less 方法很像呢,确实!多次链式调用 sass 方法同时编译出多个 CSS 文件也是 OK 的:

mix.sass('resources/assets/sass/app.sass', 'public/css')
   .sass('resources/assets/sass/admin.sass', 'public/css/admin');

有没有看到,第二个 sass 方法,我们还自定义了 CSS 输出目录,前面的 less 方法也可以这么玩啊(好吧,我不说了……)。

注意啊,sass 方法底层可不是使用 Webpack 的一个什么插件,而是直接使用了 Node.js 的 node-sass 插件。同样,如果要深入定制她的话,给她传递第三个参数就 OK 了:

mix.sass('resources/assets/sass/app.sass', 'public/css', {
    precision: 5
});

Stylus

类似于 Less 和 Sass,stylus 方法用来把 Stylus 编译成 CSS:

mix.stylus('resources/assets/stylus/app.styl', 'public/css');

也可以安装额外的 Stylus 插件,比如 Rupture。首先使用 npm install rupture 安装插件,然后给 mix.stylus 配置上它。

mix.stylus('resources/assets/stylus/app.styl', 'public/css', {
    use: [
        require('rupture')()
    ]
});

PostCSS

PostCSS 是一款强大的 CSS 转换器,Laravel Mix 开箱支持它。默认,Mix 使用广受欢迎的 Autoprefixer 插件来自动添加 CSS3 第三方浏览器前缀。你也可以自由的你的应用程序中要用的任何其他插件——首先,使用 NPM 安装插件,然后在 webpack.mix.js 中引用它:

mix.sass('resources/assets/sass/app.scss', 'public/css')
   .options({
        postCss: [
            require('postcss-css-variables')()
        ]
   });

纯 CSS

如果是要把多个纯 CSS 样式文件合并成一个,就用 styles 方法:

mix.styles([
    'public/css/vendor/normalize.css',
    'public/css/vendor/videojs.css'
], 'public/css/all.css');

URL 处理

Laravel Mix 构建在 Webpack 之上,所以需要先理解几个 Webpack 的概念。编译 CSS 时,Webpack 会重写和优化样式表里的 url() 规则。想象我们要编译的 Sass 文件里使用了相对路径引入了图片文件:

.example {
    background: url('../images/example.png');
}

注意,url() 里使用绝对路径引入图片的情况不会被处理。比如,url('/images/thing.png')url('http://example.com/images/thing.png') 就保持原样,不被处理。

默认,Laravel Mix 和 Webpack 会找到 eaxmple.png 文件,把它复制到 public/images 目录下,然后重写样式表里的 url() 规则。比如,你的 CSS 会被编译为:

.example {
  background: url(/images/example.png?d41d8cd98f00b204e9800998ecf8427e);
}

如果你不需要重写 url() 里的规则,也可以禁用它:

mix.sass('resources/assets/app/app.scss', 'public/css')
   .options({
      processCssUrls: false
   });

webpack.mix.js 中添设定了这个选项后,url() 里的规则保持原样不变。还是下面这样:

.example {
    background: url("../images/thing.png");
}

Source Maps

编译资产时,Source Map 默认是禁用的,你可以使用 mix.sourceMaps() 方法启用它。虽然在编译时,这样会带来额外的开销,但是在你 debug 的时候非常有用。

mix.js('resources/assets/js/app.js', 'public/js')
   .sourceMaps();

使用 JavaScript

Mix 提供了几个功能来帮助您处理 JavaScript 脚本文件,例如编译 ECMAScript 2015、模块捆绑、压缩以及合并纯 JavaScript 文件。 更棒的是,这一切不需要一丁点的自定义配置,即可直接使用:

mix.js('resources/assets/js/app.js', 'public/js');

这一行代码,你就使用了:

  • ES2015 语法。
  • 模块。
  • 编译 .vue 文件。
  • 生产环境下的代码压缩。

第三方提取

把项目的脚本代码和第三方库代码合并到一起有一个缺点:很难长期缓存文件。例如,项目中单个文件代码的修改就会导致,那些第三方库代码也跟着重新编译(不管有没有改动代码),然后浏览器重新加载编译后的文件。

如果你项目中的 JavaScript 代码经常修改,你就应该考虑把第三方看库文件提取出来放在属于他们的文件里。这样一来,你项目的脚本代码的修改和重新编译,不会影响到体积较大的 vendor.js 文件。Mix 的 extract 方法就是做这个的:

mix.js('resources/assets/js/app.js', 'public/js')
   .extract(['vue'])

extract 方法的接受的数组参数里,就是你最终要放到 vendor.js 文件里的所有库和模块。使用上面的逻辑编译脚本文件后,会产出以下 3 个文件:

  • public/js/mainfest.js:Webpack 运行时清单。
  • public/js/vendor.js:第三方库。
  • public/js/app.js:项目脚本代码。

为了避免 JavaScript 错误,确保按照下面的顺序加载文件:

<script src="/js/manifest.js"></script>
<script src="/js/vendor.js"></script>
<script src="/js/app.js"></script>

React

Mix 也会自动安装 React 所需要的 Babel 插件。在你使用 mix.react() 而非 min.js()

mix.react('resources/assets/js/app.jsx', 'public/js');

在背后,Mix 会下载和引入正确的 babel-preset-react 这个 Babel 插件。

原生 JS

类似 mix.styles() 方法,你可以使用 scripts() 方法合并和压缩任意数量的 JavaScript 文件:

mix.scripts([
    'public/js/admin.js',
    'public/js/dashboard.js'
], 'public/js/all.js');

这个方法并没有使用到 Webpack 编译 JavaScript 脚本文件。

注意,还有一个 mix.babel() 方法,它是在 mix.scripts 方法的基础上做了一些的修改,但支持 ES2015 语法的编译,而且两者的方法签名都是一样的。经过 mix.babel() 方法合并的脚本文件会经过 Babel 的编译,将任何 ES2015 代码转换为浏览器支持的原生 JavaScript 代码。

自定义 Webpack 配置

在上面这些开箱提供的简单 API 背后,Laravel Mix 引用了一个预先配置的 webpack.config.js 文件,一般以便能使你尽快上手使用。有时,你可能需要手动修改此文件,引用特殊的加载程序或插件,或者使用 Stylus 而不是 Sass。 在这种情况下,您有两种选择:

合并自定义配置

Mix 提供了一个有用的 webpackConfig 方法来合并、覆盖任何简短的 Webpack 配置项。这种选择比较好,因为它不需要你复制和维护一个 webpack.config.js 副本。webpackConfig 方法接收一个对象参数,你可以在里面定义任何你想要自定义的 Webpack 配置项

mix.webpackConfig({
    resolve: {
        modules: [
            path.resolve(__dirname, 'vendor/laravel/spark/resources/assets/js')
        ]
    }
});

自定义配置文件

如果你要完全自定义你的 Webpack 配置文件的话,就要把 node_modules/laravel-mix/setup/webpack.config.js 复制到你的项目根目录下,然后修改 package.json 文件里 --config 后面的配置文件路径(--config=webpack.config.js)。

如果你采用这种方式的话,未来所有在 Mix 上的更新都要手工合并到你的自定义配置文件里。

复制文件 & 目录

copy 方法用来复制文件从一个位置到另一个位置。当你需要把 node_modules 文件夹里的文件复制到 public 下时很有用。

mix.copy('node_modules/foo/bar.css', 'public/css/bar.css');

复制整个文件夹的话,就使用 copyDirectory 方法:

mix.copyDirectory('assets/img', 'public/img');

加版本号/清除缓存

许多开发者会在编译资产文件前加上时间戳或者唯一令牌号,来强制浏览器加载最新编译的脚本文件而不是使用之前的老版本。Mix 用 version 方法来解决这个问题。

version 方法会自动给编译出来的脚本文件名附加一个 hash 值,强制浏览器加载最新编译的脚本文件。

mix.js('resources/assets/js/app.js', 'public/js')
   .version();

带版本号的脚本文件编译好后,你是不知道确切的文件名的。这是使用 Laravel 全局的 mix 方法在视图里加载正确的带版本号的脚本文件。mix 方法会自动确定正确的 hash 文件:

<link rel="stylesheet" href="{{ mix('/css/app.css') }}">

因为版本文件在开发期间不常使用,所以可以设定只在执行 npm run production 命令时才给资产文件添加版本号:

mix.js('resources/assets/js/app.js', 'public/js');

if (mix.inProduction()) {
    mix.version();
}

浏览器同步加载

BrowserSync 可以用来自动监听文件改变,在不需要手工刷新的情况下,在浏览器中同步加载更新的文件内容。这时,你要使用 mix.browserSync() 方法做到:

mix.browserSync('my-domain.dev');

// Or...

// 更多配置项参考:https://browsersync.io/docs/options
mix.browserSync({
    proxy: 'my-domain.dev'
});

browserSync 方法接收的参数可以是一个(代理)字符串或者是(BrowserSync 配置)对象。接下来,当你使用 npm run watch 命令后,每当你修改一个脚本或者 PHP 文件,在浏览器中的页面都会立即更新。

环境变量

我们可以向 Mix 中注入环境变量,这是通过在 .env 文件中设定以 MIX_ 作为前缀的 Key 做到的:

MIX_SENTRY_DSN_PUBLIC=http://example.com

.env 文件中,定义好变量后,就可以通过 process.env 对象获得这个变量。如果你在 watch 时改变了这个值,是需要停止、然后重启 watch 才能让变化后的变量值生效。

process.env.MIX_SENTRY_DSN_PUBLIC

通知

如果可用的话,Mix 会在每次编译任务完成后,自动操作系统级别的通知, 给你即时的反馈说编译是否成功了。 不过,如果你希望停用这些通知——比如,你遇到过在你的生产环境的服务器上这可能会再一次触发 Mix,这时,就需要用 disableNotifications 方法禁用它了。

 mix.disableNotifications();