Laravelのブレードでは、HTMLを出力するテンプレートをモジュール化できます。ブレードのディレクティブである@yieldを使用すれば、レイアウトのテンプレートと、その中身のテンプレートを違うファイルとして管理が可能です。今回は、それを利用して会員登録の画面にインラインのjavascriptを埋め込みます。

@yield(‘script’)

まず、レイアウトのブレード、app.blade.phpを変更します。

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ config('app.name', 'Laravel') }}</title>

    <!-- Scripts -->
    <script src="{{ asset('js/app.js') }}" defer></script>
    <script type="module">
        @yield('script')
    </script>

    <!-- Fonts -->
    <link rel="dns-prefetch" href="//fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">

    <!-- Styles -->
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
    <div id="app">
...
       <main class="py-4">
            @yield('content')
        </main>
    </div>
</body>
</html>

@yield('script')には、@yield('content')と同様に、以下の会員登録画面のブレードの@section@endsectionで囲まれるコードが入ります。

@extends('layouts.app')

@section('script')
$(document).ready(function() {
    $(':button[type="submit"]').prop('disabled', true);
    $('input[type="text"]').keyup(function() {
        if($(this).val() != '') {
            $(':button[type="submit"]').prop('disabled', false);
        }
    });
});
@endsection

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ __('Register') }}</div>

                <div class="card-body">

                    <form method="POST" action="{{ route('register') }}">
                        @csrf

                        <div class="form-group row">
                            <label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Name') }}</label>

                            <div class="col-md-6">
                                <input id="name" type="text" class="form-control @error('name') is-invalid @enderror" name="name" value="{{ old('name') }}" required autocomplete="name" autofocus>

                                @error('name')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>
...
                        <div class="form-group row mb-0">
                            <div class="col-md-6 offset-md-4">
                                <button type="submit" class="btn btn-primary">
                                    {{ __('Register') }}
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

このjqueryのコードは、「Register」のボタンを最初アクセス不可としておいて、項目に1つでも入力があればアクセス可能とします。

会員画面として出力されるHTMLは以下のようになります。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="5hVqkt6ozgXZZqFlvS7TeoQlqXOyfGCqt5j3KTEz">

    <title>Laravel</title>

    <!-- Scripts -->
    <script src="/l58/js/app.js" defer></script>
    <script type="module">
$(document).ready(function() {
    $(':button[type="submit"]').prop('disabled', true);
        $('input[type="text"]').keyup(function() {
        if($(this).val() != '') {
            $(':button[type="submit"]').prop('disabled', false);
        }
    });
});
    </script>

    <!-- Fonts -->
    <link rel="dns-prefetch" href="//fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">

    <!-- Styles -->
    <link href="/l58/css/app.css" rel="stylesheet">
</head>
<body>
...

type=”module”

気づいたと思いますが、レイアウトのapp.blade.phpにおけるインラインの<script type=”module”>のtype="module"は何でしょう?

それを外して、

...
    <!-- Scripts -->
    <script src="{{ asset('js/app.js') }}" defer></script>
    <script>
        @yield('script')
    </script>
...

として実行してみると、以下のようなエラーとなってしまいます。せっかく埋めたjavascriptが効いていなく、画面の「Register」ボタンも無効表示ではなくアクセス可能となっています。

エラーは、$が定義されていない、つまりjqueryがロードされていないことによるエラーです。

いったい何が起こっているかというと、app.jsはdeferの属性があるので画面の要素がすべてロードされないと実行されません。しかし、インラインの方は何もないので、app.jsを待たずにすぐに実行されてしまったからです。

ということは、

...
    <!-- Scripts -->
    <script src="{{ asset('js/app.js') }}" defer></script>
    <script defer>
        @yield('script')
    </script>
...

deferをインラインにも入れればいいのでは?と思いますが、deferは残念ながら外部のファイルを指定したときにしか効果ありません。つまり、インラインではdeferがないとの同じことなので、つまりスクリプトエラーとなってしまいます。

一方、最初で使用した、type="module"は、スクリプトの読み込みその時点で開始されますが、deferと同様に実行は画面の要素がすべてロードされたのちに実行されます。それゆえに、app.jsが実行されて、次にインラインが実行され、期待通りの結果となります。

...
    <!-- Scripts -->
    <script src="{{ asset('js/app.js') }}" defer></script>
    <script type="module">
        @yield('script')
    </script>
...

type="module"が、deferと同じなら、今まで馴染みだった、$(document).readyも要りませんね。

@section('script')
    $(':button[type="submit"]').prop('disabled', true);
    $('input[type="text"]').keyup(function() {
        if($(this).val() != '') {
            $(':button[type="submit"]').prop('disabled', false);
        }
    });
@endsection

実行の順番

上の例のように、javascriptが実行される順番を正しく把握するには、意外と頭を使うということわかりました。
さて、以下はどう実行されるでしょうか?

<script type="module">
  console.log('インライン1');
</script>

<script src="1.js"></script>

<script defer>
   console.log('インライン2');
</script>

<script src="2.js" defer></script>

答えは、

1.js
インライン2
インライン1
2.js

By khino