在 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个字体加载错误:

file

而文件的实际打包路径是:Demo/public/assets/main/fonts

file

各种百度,谷歌后没找到有用的信息。有如下相关信息:

iview icon打包问题 #1499

这个里面有个也许可行的方案,但是配置的时候找不到 utils ,utils 是 require进去的。我没找到这个 utils 的具体文件内容

segmentfault: iview 打包之后 找不到自带的icon图片,而且路径重复,点解

剩下还查过很多,都没有什么用处。

关键词 iview webpack fonts 自行组合搜索。

上述过程失败后,询问了前端的朋友,给出了一些他的建议,后来还是失败了。然后开始怀疑自己的配置 loader 并没有生效。

开始查看源码:
在文件 ./node_modules/laravel-mix/src/builder/webpack-rules.js 搜索 ttf 看到 96 - 114 行的如下内容。

file

立刻明白了。自己的配置因为 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 行,看到如下内容:

file

说明我们可以直接用 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 协议》,转载必须注明作者和本文链接
Study hard and make progress every day. Study hard and make progress every day.
本帖由系统于 7年前 自动加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
讨论数量: 13

老牟子,没想到在这能看你。

7年前 评论
mouyong

@努力做个技术男 不怎么活跃,^_^

7年前 评论

用 laravel + vue 的话,请问你后台的登陆是怎么设计的?

7年前 评论
mouyong

@maxrisk 我没用 laravel 来做分离。我觉得,登录接口获取需要的 token,然后其他的请求都使用这个 token 去请求。而除了登录外的接口都需要在中间件验证是否拥有 token。laravel 是在 header 中 发送自定义 header 头 Authorization: 路由保护

7年前 评论

@蜗牛 独立laravel的话,前台模版中的 mix函数你是要自己封装的吧。

7年前 评论
mouyong

@496604841 自己封装多麻烦丫!?虽然涉及到的代码不多。封装了一次,自己再次看,那个路径获取什么的自己都看不懂了。后来看到其他人在非 public/js 目录下用 mix 取文件。我就选者了模仿。直接用 laravel 的 mix 函数了。改了2~3行。重新看也能看着不那么费劲

7年前 评论
mouyong

@496604841 这是我的。加了个 rootDir


if (! function_exists('mix')) {
    /**
     * Get the path to a versioned Mix file.
     *
     * @param  string  $path
     * @param  string  $manifestDirectory
     * @param  string  $rootDir
     * @return \Illuminate\Support\HtmlString
     *
     * @throws \Exception
     */
    function mix($path, $manifestDirectory = '', $rootDir = '/public')
    {
        static $manifests = [];

        if (! Str::startsWith($path, '/')) {
            $path = "/{$path}";
        }

        if ($manifestDirectory && ! Str::startsWith($manifestDirectory, '/')) {
            $manifestDirectory = "/{$manifestDirectory}";
        }

        if (file_exists(public_path($manifestDirectory.'/hot'))) {
            return new HtmlString("//localhost:8080{$path}");
        }

        $manifestPath = public_path($manifestDirectory.'/mix-manifest.json');

        if (! isset($manifests[$manifestPath])) {
            if (! file_exists($manifestPath)) {
                throw new Exception('The Mix manifest does not exist.');
            }

            $manifests[$manifestPath] = json_decode(file_get_contents($manifestPath), true);
        }

        $manifest = $manifests[$manifestPath];

        if (! isset($manifest[$path])) {
            report(new Exception("Unable to locate Mix file: {$path}."));

            if (! app('config')->get('app.debug')) {
                return $path;
            }
        }

        return new HtmlString($rootDir . $manifestDirectory.$manifest[$path]);
    }
}
7年前 评论

@蜗牛 我之前的弄的时候,整个站点都是静态的,这么做的,就不行了,只能根据webpack来弄了

7年前 评论
mouyong

@496604841 恩。

7年前 评论
  • 我用的是下面的方法
mix.copyDirectory('node_modules/iview/dist/styles/', 'public/css');
7年前 评论
mouyong

@luffyzhao 感觉有点取巧了?

7年前 评论

@蜗牛

我只是觉得你那个有点麻烦,不过能达到的效果就好了。

7年前 评论
mouyong

@luffyzhao 我会这么干是因为在代码里面 import 字体后,会在我不需要的目录下产生 fonts 目录,而你这行代码的确可以做到,只是我当时并不想让它多出一个没必要的目录以及目录下的文件。

7年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
打杂的 @ 某某医学
文章
35
粉丝
63
喜欢
328
收藏
112
排名:37
访问:19.1 万
私信
所有博文
社区赞助商