在 Laravel 之外使用 Laravel-mix 的一点小坑,字体正确无法加载。
背景
项目不是 Laravel,需要写前端代码,然后 webpack 的配置不熟悉,所以选择了使用 laravel-mix 来编译前端代码。
前端选择的是 iview 来写页面。
使用 iview 需要 import 'iview/dist/styles/iview.css';
问题来源与解决过程
webpack.mix.js 的配置内容如下:
const fs = require('fs')
const mix = require('laravel-mix')
const path = require('path')
const webpack = require('webpack')
/**
* webpack 配置
*/
mix.webpackConfig({
output: {
// 依据该路径进行编译以及异步加载
publicPath: 'assets/main/',
// 注意开发期间不加 hash,以免自动刷新失败
chunkFilename: `js/chunk[name].${ mix.inProduction() ? '[chunkhash].' : '' }js`
},
plugins: [
// 不打包 moment.js 的语言文件以减小体积
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
]
})
/**
* SPA 资源编译
*/
mix
.setPublicPath(path.normalize('Demo/public/assets/main'))
.options({
processCssUrls: false
})
.js('resources/assets/main/js/app.js', 'js')
.extract([
'axios',
'lodash',
'vue',
'vue-router',
])
.autoload({
vue: ['Vue']
})
/**
* 发布时文件名打上 hash 以强制客户端更新
*/
if (mix.inProduction()) {
mix.version()
} else {
mix.disableNotifications()
}
// 加载 .env 中 browserSync proxy 的配置
let paths = fs.readFileSync('.env', 'utf8').match(/BROWSERSYNC_PROXY=\S*/)
let proxy = paths ? paths[0].split('=')[1] : 'server.test'
/**
* 本地调试 dev-server 配置
*/
mix.browserSync({
proxy,
files: [
'resources/assets/js/**/*.js',
'Demo/Site/View/**/*.tpl.php',
]
})
.disableNotifications()
最后编译后的访问结果是在控制台中获取到如下2个字体加载错误:
而文件的实际打包路径是:Demo/public/assets/main/fonts
各种百度,谷歌后没找到有用的信息。有如下相关信息:
这个里面有个也许可行的方案,但是配置的时候找不到 utils ,utils 是 require进去的。我没找到这个 utils 的具体文件内容
segmentfault: iview 打包之后 找不到自带的icon图片,而且路径重复,点解
剩下还查过很多,都没有什么用处。
关键词 iview
webpack
fonts
自行组合搜索。
上述过程失败后,询问了前端的朋友,给出了一些他的建议,后来还是失败了。然后开始怀疑自己的配置 loader 并没有生效。
开始查看源码:
在文件 ./node_modules/laravel-mix/src/builder/webpack-rules.js
搜索 ttf
看到 96 - 114 行的如下内容。
立刻明白了。自己的配置因为 test 与 默认的不同。所以不会覆盖默认的配置。
所以进行了如下操作:
将该 rules 的 loader 段加入项目的 webpack.mix.js 中,并将 publicPath
修改为实际访问的地址 '/public/assets/main' + Config.resourceRoot
/**
* webpack 配置
*/
mix.webpackConfig({
output: {
// 依据该路径进行编译以及异步加载
publicPath: 'assets/main/',
// 注意开发期间不加 hash,以免自动刷新失败
chunkFilename: `js/chunk[name].${ mix.inProduction() ? '[chunkhash].' : '' }js`
},
plugins: [
// 不打包 moment.js 的语言文件以减小体积
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
],
module: {
rules: [
{
test: /\.(woff2?|ttf|eot|svg|otf)$/,
loader: 'file-loader',
options: {
publicPath: '/public/assets/main' + Config.resourceRoot
}
}
]
}
})
重写 运行 yarn prod 后,控制台错误消失。问题解决。
附上第一次成功试验的完整文件内容
更完美的做法在文章尾部
const fs = require('fs')
const mix = require('laravel-mix')
const path = require('path')
const webpack = require('webpack')
/**
* webpack 配置
*/
mix.webpackConfig({
output: {
// 依据该路径进行编译以及异步加载
publicPath: 'assets/main/',
// 注意开发期间不加 hash,以免自动刷新失败
chunkFilename: `js/chunk[name].${ mix.inProduction() ? '[chunkhash].' : '' }js`
},
plugins: [
// 不打包 moment.js 的语言文件以减小体积
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
],
module: {
rules: [
{
test: /\.(woff2?|ttf|eot|svg|otf)$/,
loader: 'file-loader',
options: {
publicPath: '/public/assets/main' + Config.resourceRoot
}
}
]
}
})
/**
* SPA 资源编译
*/
mix
.setPublicPath(path.normalize('Demo/public/assets/main'))
.options({
processCssUrls: false
})
.js('resources/assets/main/js/app.js', 'js')
.extract([
'axios',
'lodash',
'vue',
'vue-router',
])
.autoload({
vue: ['Vue']
})
/**
* 发布时文件名打上 hash 以强制客户端更新
*/
if (mix.inProduction()) {
mix.version()
} else {
mix.disableNotifications()
}
// 加载 .env 中 browserSync proxy 的配置
let paths = fs.readFileSync('.env', 'utf8').match(/BROWSERSYNC_PROXY=\S*/)
let proxy = paths ? paths[0].split('=')[1] : 'server.test'
/**
* 本地调试 dev-server 配置
*/
mix.browserSync({
proxy,
files: [
'resources/assets/js/**/*.js',
'Demo/Site/View/**/*.tpl.php',
]
})
更完美的做法
也许你看到了如下配置项:setPublicPath 就猜想有没有 setResourcePath
答案是有的:在源码文件 node_modules/laravel-mix/src/Api.js
中搜索 setResourcePath
来到 400-409 行,看到如下内容:
说明我们可以直接用 setResourcePath 来设置我们的资源路径
mix.setResourceRoot('/public/assets/main/')
请不要漏掉最前面与最后一级文件的 /
,原因自行试验
附上最终文件的完整文件内容
const fs = require('fs')
const mix = require('laravel-mix')
const path = require('path')
const webpack = require('webpack')
/**
* webpack 配置
*/
mix.webpackConfig({
output: {
// 依据该路径进行编译以及异步加载
publicPath: 'assets/main/',
// 注意开发期间不加 hash,以免自动刷新失败
chunkFilename: `js/chunk[name].${ mix.inProduction() ? '[chunkhash].' : '' }js`
},
plugins: [
// 不打包 moment.js 的语言文件以减小体积
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
]
})
mix.setResourceRoot('/public/assets/main/')
/**
* SPA 资源编译
*/
mix
.setPublicPath(path.normalize('Demo/public/assets/main'))
.options({
processCssUrls: false
})
.js('resources/assets/main/js/app.js', 'js')
.extract([
'axios',
'lodash',
'vue',
'vue-router',
])
.autoload({
vue: ['Vue']
})
/**
* 发布时文件名打上 hash 以强制客户端更新
*/
if (mix.inProduction()) {
mix.version()
} else {
mix.disableNotifications()
}
// 加载 .env 中 browserSync proxy 的配置
let paths = fs.readFileSync('.env', 'utf8').match(/BROWSERSYNC_PROXY=\S*/)
let proxy = paths ? paths[0].split('=')[1] : 'server.test'
/**
* 本地调试 dev-server 配置
*/
mix.browserSync({
proxy,
files: [
'resources/assets/js/**/*.js',
'Demo/Site/View/**/*.tpl.php',
]
})
附上 laravel-mix 文档
https://github.com/JeffreyWay/laravel-mix/...
本作品采用《CC 协议》,转载必须注明作者和本文链接
老牟子,没想到在这能看你。
@努力做个技术男 不怎么活跃,^_^
用 laravel + vue 的话,请问你后台的登陆是怎么设计的?
@maxrisk 我没用 laravel 来做分离。我觉得,登录接口获取需要的 token,然后其他的请求都使用这个 token 去请求。而除了登录外的接口都需要在中间件验证是否拥有 token。laravel 是在 header 中 发送自定义 header 头 Authorization: 路由保护
@蜗牛 独立laravel的话,前台模版中的 mix函数你是要自己封装的吧。
@496604841 自己封装多麻烦丫!?虽然涉及到的代码不多。封装了一次,自己再次看,那个路径获取什么的自己都看不懂了。后来看到其他人在非 public/js 目录下用 mix 取文件。我就选者了模仿。直接用 laravel 的 mix 函数了。改了2~3行。重新看也能看着不那么费劲
@496604841 这是我的。加了个 rootDir
@蜗牛 我之前的弄的时候,整个站点都是静态的,这么做的,就不行了,只能根据webpack来弄了
@496604841 恩。
@luffyzhao 感觉有点取巧了?
@蜗牛
@luffyzhao 我会这么干是因为在代码里面 import 字体后,会在我不需要的目录下产生 fonts 目录,而你这行代码的确可以做到,只是我当时并不想让它多出一个没必要的目录以及目录下的文件。