[教程] 將預設前端套件升級為 Bootstrap 4.0.0-beta

在 2017 年 8 月 10 日,Bootstrap 終於迎來了久違的 4.0.0-beta 版。

相較於 alpha 版本,beta 版少了很多地雷。同時,較 v3.3.x 版本也增進了排版的方便程度與簡化開發的難度。

本篇文章不針對 Bootstrap v4 做多教學,有興趣的可參閱官方文件或由六角學院翻譯的繁體中文手冊


範例 Repository BS4Starter


範例圖
file
file
file
file


Step 1. 準備一個乾淨的 Laravel 5.5 環境 0467e66

$ laravel new BS4Starter

$ composer create-project laravel/laravel --prefer-dist BS4Starter "5.5.*"

Step 2. 移除所有預設的前端套件 0170d8f

$ php artisan preset none

Step 3. 移除所有 npm packages 並新增所需要的 package 3fb4985

$ yarn remove axios cross-env laravel-mix lodash
$ yarn add axios cross-env laravel-mix lodash jquery popper.js bootstrap@4.0.0-beta

註:這邊移除 axioscross-envlaravel-mixlodash 後又重新安裝的理由有兩個:

  1. 為了確定安裝的套件為最新版本(若使用 laravel new BS4Starter 指令建立 Laravel Project,其 package 版本可能為舊版)
  2. 在 Laravel 預設中認為,axioscross-envlaravel-mixlodash 是在開發時期才需要被引入的,而在發佈出版本之前就應該將 public/csspublic/js 編譯為 production 模式再發佈,我認為這樣的開發方法不夠妥當(我不認為編譯後的 css 與 js 應該被放入 git 中)

註2:除了 jquery 之外,bootstrap v4 需要 popper.js 去達成 dropdown 的功能,所以記得引入。
不要引入 popper,popper 跟 popper.js 是兩個完全不同的專案,除非你有需要,否則不要引入。

Step 4. 修改預設的 js 與 sass e2e0fab

resource/assets/js/bootstrap.js

// 省略…

try {
    window.$ = window.jQuery = require('jquery');

    require('bootstrap-sass');
} catch (e) {}

// 省略…

改為

// 省略…

import 'bootstrap';

// 省略…

resources/assets/sass/app.scss 中加入以下內容

// Bootstrap
@import "node_modules/bootstrap/scss/bootstrap.scss";

然後修改 webpack.mix.js,修改範例如下所示:

let mix = require('laravel-mix');
let webpack = require('webpack');

mix.webpackConfig({
    plugins: [new webpack.ProvidePlugin({
        $: 'jquery',
        jQuery: 'jquery',
        Popper: ['popper.js', 'default']
    })]
});

mix.js('resources/assets/js/app.js', 'public/js')
   .sass('resources/assets/sass/app.scss', 'public/css');

最後安裝套件並編譯

$ yarn install
$ yarn run dev 

Step 5. 建立基礎認證樣板(Authentication Template)e599559

$ php artisan make:auth

Step 6. 修改認證樣板 73b1902

這個工程比較浩大一些,不過有一些基本的邏輯可循

  1. 沒有 panel,用 card 取而代之
  2. 沒有 offset 系列

home.blade.php 為例

  1. 將所有的 panel 取代為 card (PHPStorm 中用 ⌘+R 叫出 Replace)
  2. 移除 card-default
  3. card-heading 重命名為 card-header
  4. 移除 col-md-offset-2
  5. <div class="row"> 中加入 justify-content-md-center 使其成為 <div class="row justify-content-md-center">

修改前

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">Dashboard</div>

                <div class="panel-body">
                    @if (session('status'))
                        <div class="alert alert-success">
                            {{ session('status') }}
                        </div>
                    @endif

                    You are logged in!
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

修改後

@extends('layouts.app')

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

                <div class="card-body">
                    @if (session('status'))
                        <div class="alert alert-success">
                            {{ session('status') }}
                        </div>
                    @endif

                    You are logged in!
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

對於有表單的內容,如 login.blade.phpregister.blade.php

一步步講有點複雜,直接比對一下修改前後吧

修改前

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">Login</div>

                <div class="panel-body">
                    <form class="form-horizontal" method="POST" action="{{ route('login') }}">
                        {{ csrf_field() }}

                        <div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}">
                            <label for="email" class="col-md-4 control-label">E-Mail Address</label>

                            <div class="col-md-6">
                                <input id="email" type="email" class="form-control" name="email" value="{{ old('email') }}" required autofocus>

                                @if ($errors->has('email'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('email') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group{{ $errors->has('password') ? ' has-error' : '' }}">
                            <label for="password" class="col-md-4 control-label">Password</label>

                            <div class="col-md-6">
                                <input id="password" type="password" class="form-control" name="password" required>

                                @if ($errors->has('password'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('password') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group">
                            <div class="col-md-6 col-md-offset-4">
                                <div class="checkbox">
                                    <label>
                                        <input type="checkbox" name="remember" {{ old('remember') ? 'checked' : '' }}> Remember Me
                                    </label>
                                </div>
                            </div>
                        </div>

                        <div class="form-group">
                            <div class="col-md-8 col-md-offset-4">
                                <button type="submit" class="btn btn-primary">
                                    Login
                                </button>

                                <a class="btn btn-link" href="{{ route('password.request') }}">
                                    Forgot Your Password?
                                </a>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

修改後

@extends('layouts.app')

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

                <div class="card-body">
                    <form method="POST" action="{{ route('login') }}">
                        {{ csrf_field() }}

                        <div class="form-group">
                            <label for="email">E-Mail Address</label>

                            <input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required autofocus>

                            @if ($errors->has('email'))
                                <div class="invalid-feedback">
                                    <strong>{{ $errors->first('email') }}</strong>
                                </div>
                            @endif
                        </div>

                        <div class="form-group">
                            <label for="password">Password</label>

                            <input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" required>

                            @if ($errors->has('password'))
                                <div class="invalid-feedback">
                                    <strong>{{ $errors->first('password') }}</strong>
                                </div>
                            @endif
                        </div>

                        <div class="form-group">
                            <div class="checkbox">
                                <label>
                                    <input type="checkbox" name="remember" {{ old('remember') ? 'checked' : '' }}> Remember Me
                                </label>
                            </div>
                        </div>

                        <div class="form-group">
                            <button type="submit" class="btn btn-primary">
                                Login
                            </button>

                            <a class="btn btn-link" href="{{ route('password.request') }}">
                                Forgot Your Password?
                            </a>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Step 7. 重寫主模板(resoureces/views/layouts/app.blade.php11cd394

因為 Bootstrap v4 對於 nav 大幅度修改,所以主模板基本上是要全部重寫

這邊直接放上我的版本

<!DOCTYPE html>
<html lang="{{ app()->getLocale() }}">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

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

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

    <!-- Styles -->
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
<header class="mb-sm-3">
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <div class="container">
            <a class="navbar-brand" href="{{ url('/') }}">{{ config('app.name', 'Laravel') }}</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarSupportedContent">
                <ul class="navbar-nav ml-auto">
                    @if (auth()->guest())
                        <li class="nav-item">
                            <a class="nav-link" href="{{ route('login') }}">Login</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="{{ route('register') }}">Register</a>
                        </li>
                    @else
                        <li class="nav-item dropdown">
                            <a class="nav-link dropdown-toggle" href="" id="navbarDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                                {{ Auth::user()->name }} <span class="caret"></span>
                            </a>
                            <div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
                                <a class="dropdown-item" href="{{ route('logout') }}"
                                   onclick="event.preventDefault();
                                        document.getElementById('logout-form').submit();">
                                    Logout
                                </a>

                                <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
                                    {{ csrf_field() }}
                                </form>
                            </div>
                        </li>
                    @endif
                </ul>
            </div>
        </div>
    </nav>
</header>
<main>
    @yield('content')
</main>

<!-- Scripts -->
<script src="{{ asset('js/app.js') }}"></script>
</body>
</html>

至此,基本上是將所有的 Laravel Authentication Template 全部改為 Bootstrap v4。

認真來說,BSv4 的風格較 v3 來得大方許多,而且在預設顏色的表現上也相當具有突顯性。

我認為 BSv4 寫起來更加舒適。

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 7年前 自动加精
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 4
xcaptain

非常棒的工作,navbar下面的dropdown竟然要依赖popper.js,4和3之间的改动真是挺大的。

7年前 评论
xcaptain

https://github.com/twbs/bootstrap/issues/2... 作者不打算在4上兼容响应式分页,又是不兼容的改动。。。

7年前 评论

很感谢!找了两天,在这找到了。

7年前 评论
Destiny

感谢分享 :+1:

7年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!