tohokuaikiのチラシの裏

技術的ネタとか。

webpackでSASSとJavaScriptをそれぞれ別々のファイルにコンパイルしたかっただけの人生だった

今まで、SASSを使うときは1つの.scssファイルにまとめて

$ ./node_modules/node-sass/bin/node-sass ./scss/foo.scss ./htdocs/css/foo.css --output-style compressed --source-map true &

とかやってたんだけど、JavaScriptも一緒にコンパイルしたいなと。TypeScriptじゃなくてJavaScriptなんだけど、npm installとかしたやつを一緒にコンパイルしたくなったんです。今までは、Laravel mix使ってたんで素で使うっというのが無くて…

とりあえず、package.json

{
  "name": "myproject-sass",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "prod": "NODE_ENV=production webpack --mode=production",
    "dev": "NODE_ENV=development webpack --mode=development --watch"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "css-loader": "^5.1.2",
    "mini-css-extract-plugin": "^1.3.9",
    "node-sass": "^5.0.0",
    "sass-loader": "^11.0.1",
    "webpack": "^5.25.1",
    "webpack-cli": "^4.5.0",
    "webpack-fix-style-only-entries": "^0.6.1"
  },
  "dependencies": {
    "bs-custom-file-input": "^1.3.4"
  }
}

node-sass使ってるのは、いにしえのSCSSライブラリcompassを使ってるからである。

当初、sass-loaderさえあればcss-loaderいらんやろ、CSSなんて書いて無いしって思ったんだけど、いるのね。

webpack.config.js

const path = require('path');
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");
const FixStyleOnlyEntries = require("webpack-fix-style-only-entries");
const enabledSourceMap =  process.env.NODE_ENV !== 'production';

module.exports = ( env, argv ) => ({
  entry: {
      "foo_script": './src/js/foo.js',
      "foo_style": './src/scss/foo.scss'
  },
  output: {
    filename: 'js/[name].js',
    path: path.resolve(__dirname, 'htdocs')
  },
  module: {
    rules: [
        {
          test: /\.(sa|sc|c)ss$/,
          use: [
              {
                loader: MiniCSSExtractPlugin.loader,
              },
              {
                loader: "css-loader",
                options: {
                  url: false,
                  sourceMap: enabledSourceMap
                }
              },
              {
                loader: "sass-loader",
                options: {
                  sourceMap: enabledSourceMap
                }
              }
              ]
        }]
  },
  plugins: [
    new FixStyleOnlyEntries(),
    new MiniCSSExtractPlugin({
      filename: "./css/[name].css"
    })
  ],
  devtool: "inline-source-map",
});

ハマりポイント

  • devtoolを付けないと、sourceMapが有効にならなかった。
  • 本当は、foo.js foo.cssってしたかったんだけど、するとentryに同じキーになるのでできなかった。
  • じゃあ、outputのfilenameでfoo.css/foo.jsってそれぞれ指定すればいいじゃんって思ったんだけど、そうするとエラーが出てうまくいかなかった。多分、CSSにする際に一旦JavaScriptを経由してて、そのJavaScriptのファイル名が被ってしまうせいだと思う。
  • 環境変数NODE_ENVは、NODE_ENV=development ってpackage.jsonで指定しないといけなかったのを知らんかった。

これで、npm run prod/devで快適にSCSSとJavaScriptが書ける。・

コンストラクタでPHPのクラスの確認をして、interfaceでなくてもnewできないクラスを作る

interface指定するとプロパティが使えなくなってしまうのが嫌なんですよ。

ということで、selfとget_classを使ってこんな感じ。

<?php
namespace Foo;
class ArgsP // このクラスはインスタンスを作らせたくない。
{
    function __construct(... $args){
        if (self::class === get_class($this)){
            throw new \Exception('クラスは作れません。');
        }
    }
}
class ArgsC extends ArgsP
{
    function __construct(... $args){
        parent::__construct(... $args);
    }
}
$a = new ArgsC(1, 2, 3); // O.K
$b = new ArgsP(1, 2, 3); // 例外発生

PHPの可変長引数について、コンストラクタでそのまま渡せるのか確かめた

要するに、クラスを継承した時に引数の呼び出し方を変えたくないということなんだけど。

親クラスの __constructの引数の実装を知らなくても良いようにしたいということ。

ダメだったケース

<?php
class ArgsP
{
    function __construct(... $args){
        var_dump($args); 
    }
}
class ArgsC extends ArgsP
{
    function __construct(... $args){
        parent::__construct($args);
    }
}
new ArgsC(1, 2, 3);

ってすると、

array(1) {
  [0] =>
  array(3) {
    [0] =>
    int(1)
    [1] =>
    int(2)
    [2] =>
    int(3)
  }
}

ってなってしまった。当たり前やん。

呼び出す時も... を使った可変長引数を使えばO.K

こんな感じ。

<?php
class ArgsP
{
    function __construct(... $args){
        var_dump($args); 
    }
}
class ArgsC extends ArgsP
{
    function __construct(... $args){
        parent::__construct(... $args);
    }
}
new ArgsC(1, 2, 3);

これで結果が

array(3) {
  [0] =>
  int(1)
  [1] =>
  int(2)
  [2] =>
  int(3)
}

ってなる。やった~。

LaravelのBladeで生のStringをテンプレートとしてParseする

どんな要望やねん…ていうか、こんな感じですね。 laravel.io

Controllerの中で、

<?php
$foo = 'bar';
$text = view('This is {{$foo}}.')->render();
// $text = 'This is bar.';

結構この手の質問は見かけられるんだけど、どれも5年以上前ので。

ヘルパ関数ひとつ作れっていう回答

こんな感じ laracasts.com

<?php
use Symfony\Component\Debug\Exception\FatalThrowableError;

function render($__php, $__data)
{
    $obLevel = ob_get_level();
    ob_start();
    extract($__data, EXTR_SKIP);
    try {
        eval('?' . '>' . $__php);
    } catch (Exception $e) {
        while (ob_get_level() > $obLevel) ob_end_clean();
        throw $e;
    } catch (Throwable $e) {
        while (ob_get_level() > $obLevel) ob_end_clean();
        throw new FatalThrowableError($e);
    }
    return ob_get_clean();
}

このメソッドは、Controllerに入れておけばいいかな。

で、使う時は

<?php

$blade = 'Hello, {{ $planet }}!';
$php = Blade::compileString($blade);
return render($php, ['planet' => 'World']);

だって。

wpb/string-blade-compiler を使う

良いパッケージがあった。 packagist.org

2014年2月にInitial Commitでまだ開発継続中。

これで何も考えずに composer require wpb/string-blade-compiler して、Configとか不要で

<?php
        $user = Auth::user();
        $x = view(['template' => 'Email => {{$user->email}}'], ['user' => $user]);
//     $x =>  Email => admin@example.com

でいける。うーん、なんかよく動作原理がわからんけど、クソ便利。

LaravelでDateをAPIからPATCHする時

JavaScriptのDate objectが飛んでくるので ミューテタを使って

<?php
    /// 入金日
    public function setPaymentDateAttribute($value)
    {
        $this->attributes['payment_date'] = $value ? Carbon::parse($value)
            ->timezone(config('app.timezone'))->format('Y-m-d 00:00:00') : null;
    }

ってやっていい感じにしてあげないとダメっぽい。えー。面倒…