spa를 연동할 때 assets 번들링에 대해서 알 필요가 있습니다. 과거 운영 프로젝트에서 속도 및 최적화 문제로 js, css를 번들링 하기 위해 gruntjs를 사용했었는데, 요즘의 vite, webpack 프로세스와 비슷하게 동작되어 간단하게 살펴보겠습니다.

  //package.json
  "devDependencies": {
    "grunt": "~0.4.5",
    "grunt-contrib-clean": "^2.0.1",
    "grunt-contrib-concat": "^2.1.0",
    "grunt-contrib-cssmin": "^4.0.0",
    "grunt-contrib-uglify": "^5.2.2",
    "grunt-filerev": "^2.0.1",
    "grunt-filerev-replace": "^0.1.5"
  }
  
  //Gruntfile.js
  module.exports = function(grunt) {
	var originalFiles = {};

	grunt.initConfig({
		clean: {
			user: ['dist/assets/user/*'],
		},
		concat: {
			user_js: {
				src: [
					'assets/js/jquery.min.js',
					'assets/js/jquery.cookie.min.js',
					...
				],
				dest: 'dist/assets/user/user_script.js',
			},
			user_css: {
				src: [
					'assets/css/font.css',
					'assets/css/default.css',
					...
				],
				dest: 'dist/assets/user/user_style.css',
			},
		},
		cssmin: {
			user_css: {
				src: [
					'dist/assets/user/user_style.css',
				],
				dest: 'dist/assets/user/user_style.min.css',
			},
		},
		uglify: {
			user_js: {
				src: [
					'dist/assets/user/user_script.js',
				],
				dest: 'dist/assets/user/user_script.min.js',
			},
		},
		filerev: {
			options: {
				algorithm: 'md5',
				length: 8,
			},
			user_assets: {
				src: ['dist/assets/user/*.{js,css}'],
			},
		},
		filerev_replace: {
			options: {
				process_relative_path: true,
			},
			user_views: {
				options: {
					assets_root: 'dist/assets/user/',
				},
				src: ['application/views/front/include/includes.php'],
			},
		},
	});

	// Load required modules
	grunt.loadNpmTasks('grunt-contrib-clean');
	grunt.loadNpmTasks('grunt-contrib-concat');
	grunt.loadNpmTasks('grunt-contrib-uglify');
	grunt.loadNpmTasks('grunt-contrib-cssmin');
	grunt.loadNpmTasks('grunt-filerev');
	grunt.loadNpmTasks('grunt-filerev-replace');

	grunt.registerTask('saveOriginalFiles', function(viewType) {
		grunt.file.expand({ filter: 'isFile' }, 'dist/assets/' + viewType + '/*').forEach(function(file) {
			let nameWithoutHash = file.replace(/(.*)\\.[^.]+\\.(js|css)$/i, '$1.$2');
			nameWithoutHash = nameWithoutHash.replace(/\\//g, '\\\\');
			originalFiles[nameWithoutHash] = file;
		});
	});

	grunt.registerTask('replaceOriginalFiles', function(viewType) {
		let files = grunt.config.get('filerev_replace.' + viewType + '.src');

		files.forEach(function(file) {
			let content = grunt.file.read(file);
			for (let originalFile in originalFiles) {
				let revvedFile = grunt.filerev.summary[originalFile];
				if (revvedFile) {
					content = content.replace(originalFiles[originalFile].replace(/\\\\/g, '/'), grunt.filerev.summary[originalFile].replace(/\\\\/g, '/'));
				}
			}
			grunt.file.write(file, content);
		});
	});

	grunt.registerTask('user', ['saveOriginalFiles:user', 'clean:user', 'concat:user_js', 'concat:user_css', 'uglify:user_js', 'cssmin:user_css', 'filerev:user_assets', 'replaceOriginalFiles:user_views']);
};

순서는 가장 아래에 정의된 registerTask로 진행이 됩니다.

saveOriginalFiles로 이전에 빌드 되어 있는 파일들의 이름을 originalFiles변수에 저장합니다.

clean:user : 이전에 만든 번들링 파일을 모두 지웁니다.

concat:user_js, concat:user_css : 내가 작성한 js와 css를 모두 합쳐서 하나의 결과물을 만듭니다.

uglify:user_js : 자바스크립트 같은 경우에는 해독을 어렵게 만듭니다.

cssmin:user_css : css를 작게 만듭니다.

filerev_replace : <script>, <style> 등 번들링된 스크립트와 css를 import하는 곳을 지정합니다 여기서는 include.php 입니다.

replaceOriginalFiles:user_views : filerev_replace에서 지정한 include.php 파일의 내용을 돌면서 이전에 만든 번들링 파일을 찾아 모두 새로운 번들링 파일 네임으로 변경 합니다.

gruntjs 동작은 vite 빌드 방식과 굉장히 유사합니다. 다만 vite는 애플리케이션의 모든 컴포넌트 및 의존성들이 포함되어 하나의 script와 css를 가집니다.