Open TrdHuy opened 4 weeks ago
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const glob = require('glob');
const fs = require('fs');
const crypto = require('crypto');
class ReplaceJsonPathsPlugin {
constructor() {
this.assetMap = new Map();
}
apply(compiler) {
compiler.hooks.compilation.tap('ReplaceJsonPathsPlugin', (compilation) => {
// Hook into the processing of assets to capture original and hashed names
compilation.hooks.processAssets.tap({
name: 'ReplaceJsonPathsPlugin',
stage: compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
}, (assets) => {
for (const assetName in assets) {
// Chỉ xử lý các file hình ảnh
if (assetName.match(/\.(png|jpe?g|gif|svg)$/)) {
const assetInfo = compilation.getAsset(assetName);
let sourcePath = assetInfo.info.sourceFilename; // Lấy đường dẫn gốc
const newAssetPath = assetName; // Đường dẫn sau khi bị hash
// Loại bỏ 'src/' ở đầu nếu tồn tại
if (sourcePath.startsWith('src/')) {
sourcePath = sourcePath.substring(4);
}
// Chuyển đổi đường dẫn để bắt đầu bằng './'
const formattedSourcePath = './' + path.posix.normalize(sourcePath).replace(/\\/g, '/');
const formattedNewAssetPath = './' + path.posix.normalize(newAssetPath).replace(/\\/g, '/');
// Lưu vào map với key là đường dẫn gốc và value là đường dẫn đã hash
this.assetMap.set(formattedSourcePath, formattedNewAssetPath);
}
}
});
});
compiler.hooks.emit.tapAsync('ReplaceJsonPathsPlugin', (compilation, callback) => {
const assets = Object.keys(compilation.assets);
// Lấy ra danh sách các file JSON từ assets
assets.forEach((asset) => {
if (asset.endsWith('.json')) {
const assetPath = compilation.assets[asset].source();
let jsonContent = JSON.parse(assetPath);
// Thay thế đường dẫn hình ảnh trong JSON
jsonContent = jsonContent.map(item => {
// Duyệt qua tất cả các thuộc tính của đối tượng
for (let key in item) {
if (item.hasOwnProperty(key)) {
const originalValue = item[key];
const newValue = this.assetMap.get(originalValue);
if (newValue) {
item[key] = newValue; // Thay thế giá trị bằng giá trị mới nếu tìm thấy
}
}
}
return item;
});
// Ghi đè nội dung JSON đã chỉnh sửa
const updatedJsonContent = JSON.stringify(jsonContent, null, 2);
compilation.assets[asset] = {
source: () => updatedJsonContent,
size: () => updatedJsonContent.length
};
}
});
callback();
});
}
}
class RemoveLocalLinksAndScriptsPlugin {
apply(compiler) {
compiler.hooks.compilation.tap('RemoveUnwantedTagsPlugin', (compilation) => {
const HtmlWebpackPlugin = require('html-webpack-plugin');
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync(
'RemoveUnwantedTagsPlugin',
(data, callback) => {
let htmlContent = data.html;
// Loại bỏ tất cả các thẻ <script> có src bắt đầu bằng './assets/js/'
htmlContent = htmlContent.replace(/<script\b[^>]*src="\.\/assets\/js\/[^"]*"[^>]*><\/script>/gi, '');
htmlContent = htmlContent.replace(/<script\b[^>]*src="\.\/assets\/jscc\/[^"]*"[^>]*><\/script>/gi, '');
// Loại bỏ tất cả các thẻ <link> có href bắt đầu bằng './assets/css/'
htmlContent = htmlContent.replace(/<link\b[^>]*href="\.\/assets\/css\/[^"]*"[^>]*>/gi, '');
// Cập nhật lại nội dung HTML sau khi xóa
data.html = htmlContent;
callback(null, data);
}
);
});
}
}
class ReplaceClassPlugin {
constructor(classMappingsCache) {
this.classMappingsCache = classMappingsCache;
}
apply(compiler) {
compiler.hooks.compilation.tap('ReplaceClassPlugin', (compilation) => {
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync(
'ReplaceClassPlugin',
(data, callback) => {
// Sử dụng cache trực tiếp để thay thế các lớp CSS trong thuộc tính class của HTML
let htmlContent = data.html;
// Regex để tìm các thuộc tính class="..."
const classAttributeRegex = /class=["']([^"']+)["']/g;
// Thay thế các tên lớp trong thuộc tính class
htmlContent = htmlContent.replace(classAttributeRegex, (match, classNames) => {
// Tách các class ra thành một mảng
const classes = classNames.split(/\s+/);
// Thay thế các tên lớp dựa trên mappings
const replacedClasses = classes.map(originalClass => {
for (const mappings of Object.values(this.classMappingsCache)) {
if (mappings[originalClass]) {
return mappings[originalClass];
}
}
return originalClass; // Trả về lớp gốc nếu không tìm thấy trong mappings
});
// Ghép lại thành chuỗi và trả về thay thế cho thuộc tính class
return `class="${replacedClasses.join(' ')}"`;
});
// Cập nhật lại nội dung HTML
data.html = htmlContent;
callback(null, data);
}
);
});
}
}
const classMappingsCache = {};
const jsEntry = {
...glob
.sync('./src/assets/js/*.js')
.reduce((acc, entry) => {
const entryName = path.basename(entry, path.extname(entry));
acc[entryName] = path.resolve(__dirname, entry);
return acc;
}, {}),
...glob
.sync('./src/assets/si/*.si.js')
.reduce((acc, entry) => {
const entryName = path.basename(entry, path.extname(entry));
acc[entryName] = path.resolve(__dirname, entry);
return acc;
}, {}),
...glob
.sync('./src/assets/jscc/*.jscc.js')
.reduce((acc, entry) => {
const entryName = path.basename(entry, path.extname(entry));
acc[entryName] = path.resolve(__dirname, entry);
return acc;
}, {}),
};
function generateHtmlPlugins() {
const templateFiles = glob.sync('./src/**/*.html');
return templateFiles.map((item) => {
item = item.replace(/\\/g, '/');
const parts = item.split('/');
const name = parts[parts.length - 1].split('.')[0];
let relatedChunks;
// if (name == 'index') {
// relatedChunks = Object.keys(jsEntry).filter((chunkName) =>
// chunkName.includes(name)
// );
// } else {
// relatedChunks = Object.keys(jsEntry).filter((chunkName) =>
// chunkName.includes(name) || chunkName.includes('common')
// );
// }
relatedChunks = Object.keys(jsEntry).filter((chunkName) =>
chunkName.includes(name) || chunkName.includes('common')
);
relatedChunks.push('contract');
return new HtmlWebpackPlugin({
template: path.resolve(__dirname, item),
filename: `${name}.html`,
chunks: relatedChunks,
chunksSortMode: (chunk1, chunk2) => {
// Ưu tiên 'contract' đứng đầu tiên
if (chunk1 === 'contract') return -1;
if (chunk2 === 'contract') return 1;
// Ưu tiên các chunk chứa 'common' sau 'contract'
if (chunk1.includes('common') && !chunk2.includes('common')) return -1;
if (!chunk1.includes('common') && chunk2.includes('common')) return 1;
return 0; // Giữ nguyên thứ tự cho các chunk khác
},
});
});
}
const htmlPlugins = generateHtmlPlugins();
const nonHashedClassesPath = path.resolve(__dirname, 'non-hashed-classes.json');
const nonHashedClasses = JSON.parse(fs.readFileSync(nonHashedClassesPath, 'utf-8')).classes;
function generateHash(content) {
return crypto.createHash('md5').update(content).digest('hex');
}
module.exports = (env, argv) => {
const isProduction = argv.mode === 'production';
return {
mode: isProduction ? 'production' : 'development',
entry: jsEntry,
output: {
path: path.resolve(__dirname, 'dist'),
filename: isProduction ? '[contenthash].bundle.js' : '[name].js',
clean: true,
},
module: {
rules: [
{
test: /\.json$/,
type: 'asset/resource',
include: path.resolve(__dirname, 'src/data'),
generator: {
filename: 'data/[name][ext]' // Giữ nguyên tên file và cấu trúc đường dẫn
}
},
{
test: /\.html$/,
use: [{
loader: 'html-loader',
options: {
// Tùy chọn này có thể bỏ qua xử lý tự động các thẻ script và link
sources: {
list: [
// Giữ lại xử lý tự động của hình ảnh và các nguồn tài nguyên khác
{
tag: 'img',
attribute: 'src',
type: 'src',
},
{
tag: 'loading-image',
attribute: 'src',
type: 'src',
},
// Chặn việc xử lý các thẻ script và link với các đường dẫn cụ thể
{
tag: 'script',
attribute: 'src',
type: 'src',
filter: (tag, attribute, attributes) => {
const srcAttr = attributes.find(attr => attr.name === 'src');
return srcAttr ? !/\.\/assets\/js\//.test(srcAttr.value) : true;
},
},
{
tag: 'script',
attribute: 'src',
type: 'src',
filter: (tag, attribute, attributes) => {
const srcAttr = attributes.find(attr => attr.name === 'src');
return srcAttr ? !/\.\/assets\/jscc\//.test(srcAttr.value) : true;
},
},
{
tag: 'link',
attribute: 'href',
type: 'src',
filter: (tag, attribute, attributes) => {
const hrefAttr = attributes.find(attr => attr.name === 'href');
return hrefAttr ? !/\.\/assets\/css\//.test(hrefAttr.value) : true;
},
},
],
},
},
}],
},
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
{
loader: 'css-loader',
options: {
modules: {
getLocalIdent: (context, localIdentName, localName, options) => {
const hash = generateHash(
context.resourcePath + localName
);
const className = `_${hash.substring(9, 14)}_${hash.substring(0, 8)}`;
// Lưu mappings vào cache thay vì ghi tệp liên tục
const cssFileName = path.basename(context.resourcePath);
if (!classMappingsCache[cssFileName]) {
classMappingsCache[cssFileName] = {};
}
classMappingsCache[cssFileName][localName] = className;
if (nonHashedClasses.includes(localName)) {
classMappingsCache[cssFileName][localName] = localName;
return localName;
}
return className;
},
},
sourceMap: true,
importLoaders: 1,
},
},
'postcss-loader',
],
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
},
{
test: /\.(png|jpe?g|gif|svg)$/,
type: 'asset/resource',
generator: {
filename: 'assets/images/[hash][ext][query]',
},
},
],
},
plugins: [
...htmlPlugins,
new MiniCssExtractPlugin({
filename: isProduction ? '[contenthash].css' : '[name].css',
}),
new ReplaceJsonPathsPlugin(),
new RemoveLocalLinksAndScriptsPlugin(),
new ReplaceClassPlugin(classMappingsCache), // Truyền mappings vào plugin
{
apply: (compiler) => {
compiler.hooks.emit.tapAsync('SaveClassMappingsPlugin', (compilation, callback) => {
if (!isProduction) {
const jsonFilePath = path.resolve(__dirname, 'bin/classMappings.json');
fs.writeFileSync(
jsonFilePath,
JSON.stringify(classMappingsCache, null, 2)
);
}
callback();
});
}
}
],
optimization: {
minimize: isProduction,
minimizer: [
new TerserPlugin({
parallel: true,
}),
],
removeEmptyChunks: true,
},
resolve: {
extensions: ['.js', '.jsx', '.json'],
},
stats: {
children: true,
errorDetails: true,
},
devtool: isProduction ? false : 'source-map',
};
};
<head>
<link rel="stylesheet" href="./assets/css/index.css">
<link rel="stylesheet" href="./assets/css/portfolio_autoblocker.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap" rel="stylesheet">
<script defer="defer" src="./assets/jscc/customcontrol.jscc.js"></script>
<style>
hor-image-container img {
height: 350px;
margin-right: 10px;
border-radius: 8px;
pointer-events: none;
}
hor-image-container img:last-child {
margin-right: 0;
}
</style>
</head>
<body class="portfolio-container" style="color: azure;">
<div class="overview overview-container">
<h1 class="item-title">Samsung Honey Board Overview:</h1>
<p>HoneyBoard is a versatile keyboard application designed specifically for Samsung devices,
offering a smooth,
convenient,
and intuitive typing experience. With its sleek design and extensive customization options,
HoneyBoard allows users to personalize their keyboard to fit their unique style and preferences. Whether
you're typing messages, emails, or notes, HoneyBoard provides a seamless interface that enhances
productivity and comfort,
making it the perfect companion for all your Samsung devices.</p>
</div>
<div>
<hor-image-container class="image-container">
<img src="assets/images/project-1.jpg" alt="Image 1" />
<img src="assets/images/project-2.png" alt="Image 2" />
<img src="assets/images/project-3.jpg" alt="Image3" />
<img src="assets/images/project-4.png" alt="Image 4" />
</hor-image-container>
<h1>HoneyBoard Features</h1>
<h2>1. Multi-Device Compatibility</h2>
<p>HoneyBoard is optimized for use across various Samsung devices,
including smartphones,
tablets,
foldable devices (Galaxy Z Fold, Z Flip),
and wearables like the Galaxy Watch. It ensures a consistent and high-quality typing experience across all
device types.</p>
<h2>2. Integration with Samsung Pass</h2>
<p>Seamlessly integrates with Samsung Pass,
allowing users to securely store and autofill passwords directly from the keyboard. Enhances security and
convenience for logging into apps and websites.</p>
<h2>3. Spotify Plugin</h2>
<p>Includes a Spotify plugin that allows users to search for and share music directly from the keyboard without
leaving the conversation.</p>
<h2>4. Rich Media Support</h2>
<p>Offers extensive support for rich media including emojis,
GIFs,
and stickers,
enabling creative expression in messaging.</p>
<h2>5. AI-Powered Features</h2>
<p>Features AI-powered functionalities such as predictive text and voice recognition,
which enhance typing accuracy and efficiency by suggesting words based on context and converting speech to
text.</p>
<h2>6. Multilingual Support</h2>
<p>Provides robust multilingual support,
allowing users to easily switch between languages and ensuring accurate autocorrection in each language.</p>
<h2>7. Customization Options</h2>
<p>Allows users to personalize their keyboard experience through customization options like adjusting keyboard
size,
changing themes,
and rearranging key layouts.</p>
<h2>8. Accessibility Features</h2>
<p>Includes accessibility features such as high-contrast themes,
larger key sizes,
and voice input options,
making the keyboard more accessible for users with visual or motor impairments.</p>
<h2>9. Gesture Typing and Quick Access</h2>
<p>Supports gesture typing,
where users can swipe across the keyboard to type,
and offers quick access shortcuts for functions like copy-paste and undo-redo.</p>
<h2>10. Secure Folder Integration</h2>
<p>Integrated with Samsung's Secure Folder, allowing users to manage and input sensitive information securely.
</p>
<h2>11. Enhanced Clipboard Management</h2>
<p>Features advanced clipboard management,
enabling users to store and quickly access multiple items from the clipboard,
which is particularly useful for multitasking.</p>
</div>
</body>
<footer>
<script type="module" src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.esm.js"></script>
<script nomodule src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.js"></script>
<script src="./assets/js/contract.js"></script>
</footer>