# 編輯資源 Mix
[Laravel Mix](https://github.com/JeffreyWay/laravel-mix)提供了簡潔且可讀性高的 API ,用于使用幾個常見的 CSS 和 JavaScript 預處理器為應用定義 Webpack 構建步驟。可以通過簡單鏈式調用來定義資源的編譯。例如:
~~~php
mix.js('resources/assets/js/app.js', 'public/js')
.sass('resources/assets/sass/app.scss', 'public/css');
~~~
如果你曾經對于使用 Webpack 及編譯資源感到困惑和不知所措,那么相信你會愛上 Laravel Mix 。當然, Laravel 并沒有強迫你必須使用 Mix ,你可以自由使用任何你喜歡的資源編譯工具,或者不用也行。
## 安裝 & 配置
#### 安裝 Node
在開始使用 Mix 之前,必須先確保你的機器上安裝了 Node.js 和 NPM。
~~~php
node -v
npm -v
~~~
默認情況下, Laravel Homestead 會包含你需要的一切。當然,如果你沒有使用 Vagrant,就使用簡單的圖形安裝程序從[其下載頁面](https://nodejs.org/en/download/)安裝最新版本的 Node 和 NPM。
#### Laravel Mix
然后就只需要安裝 Laravel Mix。在新的 Laravel 項目中,你可以在目錄結構的根目錄下找到一個`package.json`文件,它包含了運行基本的 Mix 所需的內容。就如同`composer.json`文件,只不過它定義的是 Node 的依賴而不是 PHP。你可以使用下面的命令來安裝它引用的依賴項:
~~~php
npm install
~~~
## 運行 Mix
Mix 是位于[Webpack](https://webpack.js.org/)頂部的配置層,所以要運行 Mix 任務,只需要執行默認的 Laravel`package.json`文件中包含的一個 NPM 腳本:
~~~php
// 運行所有的 Mix 任務...
npm run dev
// 運行所有的 Mix 任務并縮小輸出...
npm run production
~~~
#### 監聽資源文件修改
`npm run watch`會在你的終端持續運行,監聽所有相關的資源文件以便進行更改。 Webpack 會在檢測到文件更改時自動重新編譯資源:
~~~php
npm run watch
~~~
在某些環境,當文件更改時, Webpack 不會更新。如果系統出現這種情況,請考慮使用`watch-poll`命令:
~~~php
npm run watch-poll
~~~
## 使用樣式
`webpack.mix.js`文件是所有資源編譯的入口點。可以把它看作是 Webpack 中的輕量級配置封裝清單。 Mix 任務可以一起被鏈式調用,以精確定義資源的編譯方式。
### Less
`less`方法可以用于將[Less](http://lesscss.org/)編譯為 CSS。在`webpack.mix.js`中這樣寫,可以將`app.less`編譯到`public/css/app.css`中。
~~~php
mix.less('resources/assets/less/app.less', 'public/css');
~~~
可以多次調用`less`方法來編譯多個文件:
~~~php
mix.less('resources/assets/less/app.less', 'public/css')
.less('resources/assets/less/admin.less', 'public/css');
~~~
如果要自定義編譯的 CSS 的文件名,可以將一個完整的路徑作為第二個參數傳給`less`方法:
~~~php
mix.less('resources/assets/less/app.less', 'public/stylesheets/styles.css');
~~~
如果你需要重寫[底層 Less 插件選項](https://github.com/webpack-contrib/less-loader#options),你可以將一個對象作為第三個參數傳到`mix.less()`:
~~~php
mix.less('resources/assets/less/app.less', 'public/css', {
strictMath: true
});
~~~
### Sass
`sass`方法可以將[Sass](http://sass-lang.com/)編譯為 CSS。用法如下:
~~~php
mix.sass('resources/assets/sass/app.scss', 'public/css');
~~~
和`less`方法一樣,你可以將多個 Sass 文件編譯到各自的 CSS 文件中,甚至可以自定義生成的 CSS 的輸出目錄:
~~~php
mix.sass('resources/assets/sass/app.sass', 'public/css')
.sass('resources/assets/sass/admin.sass', 'public/css/admin');
~~~
另外[Node-Sass 插件選項](https://github.com/sass/node-sass#options)也同樣可以作為第三個參數:
~~~php
mix.sass('resources/assets/sass/app.sass', 'public/css', {
precision: 5
});
~~~
### Stylus
類似于 Less 和 Sass,`stylus`方法可以將[Stylus](http://stylus-lang.com/)編譯為 CSS:
~~~php
mix.stylus('resources/assets/stylus/app.styl', 'public/css');
~~~
你也可以安裝其他的 Stylus 插件,例如[Rupture](https://github.com/jescalan/rupture)。首先,通過 NPM (`npm install rupture`) 來安裝插件,然后在調用`mix.stylus()`時引用它:
~~~php
mix.stylus('resources/assets/stylus/app.styl', 'public/css', {
use: [
require('rupture')()
]
});
~~~
### PostCSS
Laravel Mix 自帶了一個用來轉換 CSS 的強大工具[PostCSS](http://postcss.org/)。默認情況下, Mix 利用了流行的[Autoprefixer](https://github.com/postcss/autoprefixer)插件來自動添加需要的 CSS3 瀏覽器引擎前綴。不過,你也可以自由添加任何適合你應用的插件。首先,通過 NPM 安裝所需的插件,然后在`webpack.mix.js`文件中引用它:
~~~php
mix.sass('resources/assets/sass/app.scss', 'public/css')
.options({
postCss: [
require('postcss-css-variables')()
]
});
~~~
### 原生 CSS
如果你只是想要把一些原生 CSS 樣式合并成單個文件,你可以使用`styles`方法。
~~~php
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 文件:
~~~php
.example {
background: url('../images/example.png');
}
~~~
> {note} 任何給定`url()`的絕對路徑會被排除在 URL 重寫之外。例如`url('/images/thing.png')`或`url('http://example.com/images/thing.png')`不會被修改。
默認情況下, Laravel Mix 和 Webpack 會找到`example.png`, 然后把它復制到你的`public/images`目錄下,然后重寫生成樣式中的`url()`。這樣,編譯之后的 CSS 會變成:
~~~php
.example {
background: url(/images/example.png?d41d8cd98f00b204e9800998ecf8427e);
}
~~~
但如果你想以你喜歡的方式配置現有的文件夾結構,可以禁用`url()`的重寫:
~~~php
mix.sass('resources/assets/app/app.scss', 'public/css')
.options({
processCssUrls: false
});
~~~
在你的`webpack.mix.js`文件像上面這樣配置之后, Mix 將不再匹配`url()`或者將資源復制到你的 public 目錄。換句話說,編譯后的 CSS 會和原來輸入的一樣:
~~~php
.example {
background: url("../images/thing.png");
}
~~~
### 資源映射
默認情況下資源映射是禁用的,可以在`webpack.mix.js`文件中調用`mix.sourceMaps()`方法來開啟它。盡管它會帶來一些編譯/性能的成本,但在使用編譯資源時,可以為使用瀏覽器的開發人員工具提供額外的調試信息。
~~~php
mix.js('resources/assets/js/app.js', 'public/js')
.sourceMaps();
~~~
## 使用 JavaScript
Mix 提供了一些函數來處理 JavaScript 文件,像是編譯 ECMAScript 2015、模塊綁定、壓縮以及簡單地合并處理原生 JavaScript 文件。更棒的是,這些操作都不需要進行任何自定義的配置:
~~~php
mix.js('resources/assets/js/app.js', 'public/js');
~~~
僅僅這上面的一行代碼,就支持:
* ES2015 語法
* 模塊
* 編譯`.vue`文件
* 生產環境壓縮代碼
### 提取 Vendor
將應用特定的 JavaScript 與依賴庫捆綁在一起有個潛在的缺點,會使得長期緩存更加困難。例如,即使應用使用的依賴庫沒有被更改,只要有代碼被單獨更新,都會強制瀏覽器重新下載所有依賴庫。
如果你打算頻繁更新應用的 JavaScript,應該考慮將所有的依賴庫提取到自己的文件中。這樣一來,應用代碼的更改就不會影響到大型`vendor.js`文件的緩存。而 Mix 的`extract`方法能使之變得輕而易舉:
~~~php
mix.js('resources/assets/js/app.js', 'public/js')
.extract(['vue'])
~~~
`extract`方法接受一個數組參數。這個數組是要提取到`vendor.js`文件中的所有的依賴庫或模塊。比如上面的例子中,Mix 將生成以下文件:
* `public/js/manifest.js`:*Webpack 運行清單*
* `public/js/vendor.js`:*第三方庫*
* `public/js/app.js`:*應用代碼*
務必按照以下文件順序加載,以防 JavaScript 報錯:
~~~php
<script src="/js/manifest.js"></script>
<script src="/js/vendor.js"></script>
<script src="/js/app.js"></script>
~~~
### React
Mix 會自動安裝 React 必要的 Babel 插件。只需替換`mix.js()`為`mix.react()`:
~~~php
mix.react('resources/assets/js/app.jsx', 'public/js');
~~~
Mix 會在后臺自動下載包括`babel-preset-react`Babel 插件在內的庫。
### 原生 JS
類似`mix.styles()`合并樣式表文件,`scripts()`方法可以合并壓縮任意數量的 JavaScript 文件:
~~~php
mix.scripts([
'public/js/admin.js',
'public/js/dashboard.js'
], 'public/js/all.js');
~~~
對那些沒有用 Webpack 編譯 JavaScript 的舊項目來說,這個方法相當實用。
> {tip}`mix.babel()`和`mix.scripts()`還是有些差別的。在使用上與`scripts`一致,但是,所有文件都會經過 Babel 編譯,任何 ES2015 語法的代碼都會編譯成所有瀏覽器都支持的原生 JavaScript。
### 自定義 Webpack 配置
Laravel Mix 在背后引用一個預配置的`webpack.config.js`文件加速啟動與運行。有時,可能有一個特殊的加載器或需要引用的插件,或傾向于 Stylus 而不是 Sass,而不得不去手動修改這個文件。這種情況下,有兩種選擇:
#### 合并自定義配置
Mix 提供了一個`webpackConfig`方法來合并任何 Webpack 配置以覆蓋默認配置。因此你不需要復制和維護`webpack.config.js`的文件副本。`webpackConfig`方法接受一個包含任何想要應用的[Webpack 配置項](https://webpack.js.org/configuration/)的對象:
~~~php
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`的值指向新復制的配置文件。采用這種方法進行自定義,如果后續 Mix 版本有更新,需要手動合并`webpack.config.js`并到你的自定義文件中。
## 復制文件和目錄
`copy`方法用于將文件和目錄復制到新位置。當`node_modules`目錄中的特定資源需要被重定位到`public`文件夾時會很有用。
~~~php
mix.copy('node_modules/foo/bar.css', 'public/css/bar.css');
~~~
復制目錄時,`copy`方法會平面化目錄的結構。要維護目錄的原始結構,應該使用`copyDirectory`方法:
~~~php
mix.copyDirectory('assets/img', 'public/img');
~~~
## 版本控制 / 緩存清除
許多的開發者會對其編譯的資源文件加上時間戳或唯一的令牌作為后綴,以此來強迫瀏覽器加載全新的資源文件,而不是舊版本的代碼副本。你可以使用 Mix 的`version`方法處理它們。
`version`方法會自動為所有編譯文件的文件名附加唯一的哈希值,從而實現更方便的緩存清除功能:
~~~php
mix.js('resources/assets/js/app.js', 'public/js')
.version();
~~~
生成版本化文件后,你不會知道確切的文件名。因此,在你的[視圖](https://laravel-china.org/docs/laravel/5.7/mix/docs/laravel/5.7/views)中應該使用 Laravel 的全局輔助函數`mix`來正確加載名稱被哈希后的文件。`mix`函數會自動確定被哈希的文件名稱:
~~~php
<link rel="stylesheet" href="{{ mix('/css/app.css') }}">
~~~
因為在開發中通常是不需要版本化,你可以指示版本控制過程僅在`npm run production`運行期間進行:
~~~php
mix.js('resources/assets/js/app.js', 'public/js');
if (mix.inProduction()) {
mix.version();
}
~~~
## Browsersync 重加載
[BrowserSync](https://browsersync.io/)會自動監聽文件修改并將修改注入瀏覽器而無需手動刷新。你可以通過調用`mix.browserSync()`方法啟用該支持:
~~~php
mix.browserSync('my-domain.test');
// Or...
// https://browsersync.io/docs/options
mix.browserSync({
proxy: 'my-domain.test'
});
~~~
你可以傳遞一個字符串 (代理) 或對象 (BrowserSync 設置) 到該方法。接下來,使用`npm run watch`命令來啟動 Webpack 的開發服務器。現在,當你編輯一個 JavaScript 腳本或 PHP 文件時,會看到瀏覽器立刻刷新以響應你的修改。
## 環境變量
你可以通過在`.env`文件中添加`MIX_`前綴將環境變量注入 Mix:
~~~php
MIX_SENTRY_DSN_PUBLIC=http://example.com
~~~
在`.env`文件中定義好變量后,可以通過`process.env`對象進行訪問。如果在運行`watch`任務期間變量值有變動,需要重啟任務:
~~~php
process.env.MIX_SENTRY_DSN_PUBLIC
~~~
## 通知
正常情況下, Mix 會自動為每個捆綁顯示操作系統通知,這可以給你一個及時的反饋:編譯成功還是失敗。不過,某些場景下你可能希望禁止這些通知,一個典型的例子就是在生產環境服務器觸發 Mix。通知可以通過`disableNotifications`方法被停用:
~~~php
mix.disableNotifications();
~~~