2020年12月31日 星期四

React 練習 - 使用 css, scss, img loader, image 壓縮

自己寫的 React, Webpack 練習,

練習了以下 webpack loader:


sass-loader : 

用來處理編譯 sass 檔案。


css-loader : 

用來處理 css 檔案。


style-loader : 

用來將 css 寫進網頁中 (不用在網頁中另寫 <link rel="stylesheet" href="xxx.css" />)。


image-webpack-loader : 

用來處理圖片檔案,有圖片壓縮等功能。


url-loader :

用來將圖片的 url 寫進網頁中,內部使用了 file-loader,所以可使用 file-loader 的選項參數,

有能把圖片的 url 轉成 base64 url 的等功能。


mini-css-extract-plugin : 

用來把處理好的 css 輸出成最後版本 css 檔案的功能,

網頁要使用 css 需自行加入 css style include, Ex:

<link rel="stylesheet" href="xxx.css" />)


實作了一個簡易的 tab 選單當範例來練習:

1. 有三個選單 tab,分別對應三個內容 (暫稱 content),內容中有各自的文字及各放置了不同的圖片。

2. 當滑鼠滑入 tab 中時  (mouseenter) ,下方會切換到對應的 Content。

檔案結構如下圖所示:


重要的程式碼如下 (以下根目錄以從 package.json 檔所在位置,也就是 WebContent 資料夾算起):

/index.html :
<!DOCTYPE html>
<html>
    <head>
        
    </head>
    <body>
        <div id="root"></div>
        <script src="dist/js/index_bundle.js"></script>
    </body>
</html>
/dev/js/index.jsx :
import React from "react"
import ReactDOM from "react-dom"
import {Main} from "./components/Main.jsx"

import "@projectDirPath/dev/css/style.scss"

ReactDOM.render(<Main/>, document.getElementById("root"));
/dev/js/components/Main.jsx :
import React from "react"
import {Tab} from "./Tab.jsx"
import {Content} from "./Content.jsx"
import dogImgSrc from "@projectDirPath/dev/img/dog.jpg"
import catImgSrc from "@projectDirPath/dev/img/cat.jpg"
import bananaCatImgSrc from "@projectDirPath/dev/img/bananaCat.jpg"

function Main(){
    const [itemList, setItemList] = React.useState([
                                                        {
                                                            title: "title1",
                                                            desc: "desc1",
                                                            img: dogImgSrc,
                                                            isActive: true
                                                        },
                                                        {
                                                            title: "title2",
                                                            desc: "desc2",
                                                            img: catImgSrc,
                                                            isActive: false
                                                        },
                                                        {
                                                            title: "title3",
                                                            desc: "desc3",
                                                            img: bananaCatImgSrc,
                                                            isActive: false
                                                        }
                                                    ]);

    function setItemActive(index){
        var updatedItemList = itemList.map((item, i) => {
            item.isActive = (i == index);
            return item;
        });

        setItemList(updatedItemList);
    }

    return (
        <React.Fragment>
            <div className="tab">
                {
                    itemList.map((item, index) => {
                        return <Tab item={item} key={index} index={index} setItemActive={setItemActive}/>;
                    })
                }
            </div>
            <div className="content">
                {
                    itemList.map((item, index) => {
                        return <Content item={item} key={index}/>;
                    })
                }
            </div>
        </React.Fragment>
    )
}

export {Main}
/dev/js/components/Content.jsx :
import React from "react"

function Content({item}){
    return (
        <div className={item.isActive ? "active" : ""}>{item.desc}<img src={item.img}/></div>
    );
}

export {Content}
/dev/js/components/Tab.jsx :
import React from "react"

function Tab({item, index, setItemActive}){

    function handleMouseEnter(){
        setItemActive(index);
    }

    return (
        <div className={item.isActive ? "active" : ""} onMouseEnter={handleMouseEnter}>{item.title}</div>
    );
}

export {Tab}
/dev/css/style.scss :
.tab {
    div {
        display: inline-block;
        width: 30%;
        height: 100px;
        background-color: green;
        background-color: yellow;
        border-bottom: 3px solid black;
    }

    div.active {
        background-color: yellow;
        border-left: 3px solid black;
        border-right: 3px solid black;
        border-top: 3px solid black;
        border-bottom: 3px solid yellow;
    }
}
  
.content {
    background-color: yellow;
    border-left: 3px solid black;
    border-right: 3px solid black;
    border-bottom: 3px solid black;

    div {
        display: none;
        padding: 3px;
    }

    div.active {
        display: block;
    }

    img {
        width : 30%;
    }
 }


webpack.config.js:

  var path = require('path');
//const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
 entry : "./dev/js/index.jsx",
 output : {
  path : path.resolve(__dirname, "dist"),
  filename : "js/index_bundle.js",
  publicPath : "dist/"
 },
 resolve: {
    alias: {
        '@projectDirPath': __dirname
    }
},
 watch : false,
 devtool : 'source-map',
 mode : "development", //"production",
 module : {
  rules : [ {
   test : /\.jsx?$/,
   use : {
    loader : 'babel-loader',
    options : {
     presets : [ '@babel/preset-react', '@babel/preset-env' ]
    }
   }
  },
  {
    test: /\.(png|jpg|gif|jpe?g|svg)$/,
    use: [
      {
        loader: 'url-loader',
        options: {
          limit: 50 * 1024,
          outputPath: "img"
        }        
      },
      "image-webpack-loader"      
    ]
  },/*
  {
      test : /\.css$/,
      use : ["style-loader", "css-loader"]
  },*/
  {
    test: /\.s[ac]ss$/,
    use: [        
         "style-loader", //MiniCssExtractPlugin.loader,
         "css-loader", "sass-loader"
        ]
  }]
 }
 //, plugins: [new MiniCssExtractPlugin()]
}
  

package.json

  {
  "name": "reacttest",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack",
    "runServer": "lite-server"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.7.5",
    "@babel/preset-env": "^7.7.5",
    "@babel/preset-react": "^7.7.4",
    "babel-loader": "^8.0.6",
    "css-loader": "^5.0.1",
    "file-loader": "^6.2.0",
    "image-webpack-loader": "^7.0.1",
    "lite-server": "^2.6.1",
    "mini-css-extract-plugin": "^1.3.3",
    "node-sass": "^5.0.0",
    "sass-loader": "^10.1.0",
    "style-loader": "^2.0.0",
    "url-loader": "^4.1.1",
    "webpack": "^4.41.2",
    "webpack-cli": "^3.3.10"
  },
  "dependencies": {
    "react": "^16.12.0",
    "react-dom": "^16.12.0",
    "react-redux": "^7.2.2",
    "redux": "^4.0.5"
  }
}

  

原始碼分享下載:

ReactTest2.7z


參考:

  1. Webpack 4 筆記 (3)
  2. 新手向 Webpack 完全攻略 (7) – 編譯圖片?略懂略懂
  3. Webpack 前端打包工具 - 使用 image-webpack-loader 壓縮圖片
  4. Webpack 前端打包工具 - 使用 sass-loader 編譯 Sass/SCSS 預處理器
  5. Webpack 前端打包工具 - 使用 mini-css-extract-plugin 把 CSS 抽離出來
  6. NPM – Webpack – 使用 import 的絕對路徑方法

2020年12月2日 星期三

Java EE - JSP 自訂標籤 - 自訂 EL 函式

紀錄一下 在 JAVA EE JSP 中,

如何建立客制的、自己所使用的 "自訂EL 函式 (or 自訂 JSTL tag 標籤函式)" 。


首先先設定下範例需求:

1. 想在 JSP 中,使用如下程式碼來使用,

<%@ taglib prefix="customEL" uri="/WEB-INF/customTld.tld"%>

${customEL:contains(list, value)}

第一行使引入自行定義的 TLD 檔,

第二行是使用了在自訂的 TLD 檔裡,被設定了的一個 function, "contains(list, value)",

作用為:傳入一個 Collection 的物件, "list" 和一個 Object 的物件  (或int 之類的簡單型別), "value",

然後傳回一個 boolean 值來表示在 list 中有無存在 value ,有的話回 true、否則回 false。

底層其實就是呼叫 Collection 的 contains() ,即 return list.contains(value);


2. 開始制作自訂EL函式,首先先實作其邏輯,就是一個簡單的 Class 和其內的 method:

CustomElFunction.java:

package com.xxx.tld;

import java.util.Collection;

public class CustomElFunction {
	public static boolean contains(Collection collection, Object object) {
		return collection.contains(object);
	}
}

可以看到只是一個很普通的 Class,裡面設定了一個呼叫 Collection.contains() 的 method,要注意的是 method 必須為 static 的。
因為之後會在 TLD 檔來以 package path + Class Name + Method 簽名的方式設定 自訂EL 函式,
所以如果有想要建立多個 自訂EL函式,可以在一個 Class 裡加入多個 method,
或是再建一個 Class 加入 method 都可以。

3. 再來是建立自己的 TLD 檔案,內容如下:

customTld.tld:

<?xml version="1.0" encoding="UTF-8" ?>

<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
  version="2.0">
    
  <description>Custom JSTL functions library</description>
  <display-name>Custom JSTL functions</display-name>
  <tlib-version>1.0</tlib-version>
  <short-name>customEL</short-name>
  <uri>http://xxx.xxx.com/jsp/jstl/functions</uri>

  <function>
    <description>
      Returns true if this collection contains the specified element
    </description>
    <name>contains</name>
    <function-class>com.xxx.tld.CustomElFunction</function-class>
    <function-signature>boolean contains(java.util.Collection, java.lang.Object)</function-signature>
    <example>
      ${customEL:contains(list, value)}
    </example>  
  </function>

</taglib>

<descriptin>, <display-name>, <tlib-version>, <short-name> 等比較不重要可隨便填。
<uri> 只有在想用在 JSP 中用 uri 的方式時引入才需要,如是直接指定專案路徑下的相對位置,例如 /WEB-INF/customTld.tld , 則可以隨便填。

重要的是 <name>, <function-class>, <function-signature>,

<name> 是在 JSP 中呼叫自訂EL函式時要使用的函式名稱,
即 
${customEL:contains(list, value)}
中的 contains

<function-class> 為指定自訂EL函式所在的 Class path

<function-signature> 設定了自訂EL函式的函式方法簽名

最後,把 TLD 檔放到自己知道地方,從 JSP 中引入,就可以開始使用了,
例如如下:

xxx.jsp:
<%@ taglib prefix="customEL" uri="/WEB-INF/customTld.tld"%>

${customEL:contains(list, value)}

參考資料: