2016/01/27

webpack

將 JavaScript 模組化打包減少 http request 是目前幾乎必備的技能,之前使用 requirejs 來做這一塊,後來玩了一下 webpack 後發現回不去了,以此篇做個設定與使用筆記。

webpack.config.js
var webpack = require('webpack');
var sourcePath = __dirname + '/assets/js/src';

module.exports = {
    entry: sourcePath + '/entry.js',
    output: {
        path: __dirname + '/assets/js/build',
        filename: 'bundle.js'
    },
    plugins: [
        new webpack.ProvidePlugin({
            $: "jquery",
            jQuery: "jquery"
        }),
        new webpack.optimize.UglifyJsPlugin({
            compress: {
                warnings: false
            }
        })
    ],
    module: {
        loaders: [
            {test: /\.js$/, exclude: /node_modules/, loader: "babel", query: {presets: ['es2015']}}
        ],
    },
    resolve: {
        alias: {
            init: __dirname + '/assets/js/app/init.js'
        }
    }
};

web.config.js 是 webpack 的設定檔,將常用的東西規範好之後,執行 webpack 這個指令他便會照你的設定檔作動,entry 指的是來源檔案,如果是多個檔案要 compiler 的話可以使用物件寫法:

    entry: {
        'a': sourcePath + '/a.js',
        'b': sourcePath + '/b.js'
    },
    output: {
        path: __dirname + '/assets/js/build',
        filename: '[name].js'
    },

這樣的格式他便會照你的 key name gen 一個一模一樣的檔案在 output 資料夾,plugin 的部分我用 npm 安裝了 jQuery,並用 plugin 宣告讓他可以全域使用,不用每次都 require,壓縮的部分使用 UglifyJS 2,基本上如果不使用 plugin 的話在 command line 下 webpack --optimize-minimize 會有一樣的效果,記得如果有下壓縮編譯的話速度會慢一些,開發階段可以先關起來,module 的部分使用了 babel-loader,撰寫的程式碼都會經過 babel 的編譯,確定你的 ES6 語法可以跨瀏覽器使用,alias 的部分就是讓你少打字而已,另外如果在 command line 使用 webpack -w 便會執行監看模式,如果 entry 的檔案有所變動的話就會 auto compiler,但我喜歡自己下指令,因為我還是會稍微看一下編譯結果,接下來開一個 webpack 的資料夾,來安裝相關的模組。

npm init
npm i babel-loader babel-core babel-preset-es2015 babel-preset-react --save-dev
npm i uglify-js --save-dev
npm i jquery@1.12 --save
index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>webpack test</title>
    <script src="assets/js/build/bundle.js"></script>
</head>
<body>

</body>
</html>
assets/js/app/init.js
module.exports = {
    hello: (name = 'Chan') => {
        return `Hello world, I am ${name}`;
    }
};
assets/js/src/entry.js
var init = require('init');

$(function() {
    console.log(init.hello());
});

在 command line 執行 webpack,開啟網頁就會得到,Hello world, I am Chan,我的 VIM 有使用 JSHint 檢查原始碼,他偵測到 ES6 會報錯,只要在根目錄放置設定檔即可。

.jshintrc
{
    "esnext": true
}

2016/01/11

PHP JSON Content Type

目前大家網路交換格式大多用 json,前幾天踩到一個雷,送出需求到一個網頁以後,該頁面會將 html 的內容回吐,格式像是:

{
 "status": "done",
 "html": "<ul><li><a href=\"#\">link</a></li></ul>"
}

但當 conosole.log() 的時候內容被截斷了,出現可能只有 link</a></li> 之類的,原因很簡單,我的 output PHP 沒有給 content type,所以造成 request 頁面雖然可以解析 json format,但其實是 jQuery 硬做掉的,平時還是不要偷懶,記得要 output 什麼內容就加上該對應的 content type 吧。

// PHP
header('Content-Type: application/json; charset=utf-8');

// CodeIgniter
$this->output->set_content_type('application/json')->set_output(json_encode(compact('result')));

// Laravel 5+
use Illuminate\Http\Response;

return response(compact('result'), $status)->header('Content-Type', 'application/json');

// Or

return response()->json(compact('result'));

2016/01/08

Preview Image Before Upload

後台上傳圖片時,若想要看到預覽圖片,早期只有 IE 有 function 可以吃到 localhost 的檔案,讓你看到你要上傳的圖片樣貌,現在 HTML5 有 FileReader 的 api 可以用,範例如下:

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js"></script>
    <script type="text/javascript" charset="utf-8">
    $(function() {
        var allows = ['jpg', 'jpeg', 'gif', 'png'];

        $('#file').on('change', function() {
            var reader = new FileReader();
            var ext = $(this).val().split('.').pop().toLowerCase();

            try {
                if ($.inArray(ext, allows) === -1) {
                    throw new Error('檔案格式錯誤,僅允許' + allows.join(', '));
                }

                reader.readAsDataURL(this.files[0]);
                reader.onload = function(e) {
                    $('#content').html('<img src="' + e.target.result + '">');
                }
            } catch (e) {
                alert(e.message);
            }
        });
    });
    </script>
</head>
<body>
<input id="file" type="file" name="file">
<div id="content"></div>
</body>
</html>

這個方法有幾個問題:

  1. 瀏覽器支援問題
  2. 真實呈現問題(假設我們有透過後端處裡圖片無法呈現最後樣貌)

基於以上兩點,我決定還是透過 ajax + php 的方法來做掉,我的方法是,檔案 onChange 的時候 upload 去 server 端,請 server 端處理 tmp_name,然後傳回 base64_encode 的結果,這樣的好處是檔案我沒有使用 move 搬移,server 會自動幫我定期清除那些暫存檔,使用 base64 編碼因此不需要衍生其他檔案,做法如下:

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.form/3.51/jquery.form.min.js"></script>
    <script type="text/javascript" charset="utf-8">
    $(function() {
        $('#file').on('change', function() {
            $('#frm').ajaxSubmit({
                url: 'preview.php',
                success: function(response) {
                    if (response.status !== 'done') {
                        alert(response.message);
                    } else {
                        $('#content').html('<img src="' + response.message + '">');
                    }
                }
            });

            return false;
        });
    });
    </script>
</head>
<body>
<form id="frm" method="post" action="upload.php">
<input id="file" type="file" name="file">
</form>
<div id="content"></div>
</body>
</html>
php
<?php

header('Content-Type: application/json');

include 'vendor/autoload.php';

$status = 'done';
$message = '';
$allows = ['jpeg', 'png', 'gif'];
$file = $_FILES['file'];
$map = function($item) {
    return "image/{$item}";
};

try {
    if ($file['name'] === '') {
        throw new Exception("請選擇檔案");
    }

    $type = $file['type'];

    if (!in_array($type, array_map($map, $allows))) {
        throw new Exception('檔案格式錯誤,僅接受'.implode(', ', $allows));
    }

    $layer = PHPImageWorkshop\ImageWorkshop::initFromPath($file['tmp_name']);
    $layer->resizeInPixel(100, null, true, 0, 0, 'MM');
    $result = $layer->getResult();

    ob_start();
    call_user_func(str_replace('/', '', $type), $result);
    $image = ob_get_contents();
    ob_end_clean();

    $message = "data:{$type};base64,".base64_encode($image);
} catch (Exception $e) {
    $status = 'fail';
    $message = $e->getMessage();
}

echo json_encode(compact('status', 'message'));

我使用了 ImageWorkshop 來處理縮圖等等的功能,這個部分可以自行替換 solution。