[教程] 將預設前端套件升級為 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 协议》,转载必须注明作者和本文链接
本帖由系统于 6年前 自动加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 4
xcaptain

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

6年前 评论
xcaptain

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

6年前 评论

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

6年前 评论
Destiny

感谢分享 :+1:

6年前 评论

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