As a developer, I recently found myself faced with an exciting challenge: modernizing a legacy C# .NET codebase that was still using Bootstrap 3. The goal was clear - bring the project up to speed with the latest Bootstrap 5. However, I quickly realized that making such a significant leap could be risky and time-consuming.
That's when I decided to take a phased approach:
This strategy would allow for a more manageable transition, easier debugging, and a smoother overall process. Today, I'm excited to share the first part of this journey - automating the migration from Bootstrap 3 to 4 using a Python script.
Before we dive in, it's important to note that the code presented here is a simplified version of the actual script used in the project. For obvious reasons, such as proprietary information and specific project requirements, I've streamlined the code for this blog post. However, the approach and core functionality remain very similar to what was implemented in the real-world scenario.
Migrating from Bootstrap 3 to 4 involves numerous class name changes and deprecated components. Manually updating these across an entire project can be time-consuming and error-prone. That's where our Python script comes in.
Our script, which we'll call bootstrap_migrator.py, is designed to scan your project files and automatically update Bootstrap 3 class names to their Bootstrap 4 equivalents. It handles HTML, Razor (cshtml), and even JavaScript files, making it a comprehensive solution for your migration needs.
Let's dive into the details of our migration script and explain each part.
import os import re
We start by importing two essential Python modules:
def update_bootstrap_classes(content, file_type): class_mappings = { r'\bcol-xs-(\d )\b': r'col-\1', r'\bcol-sm-(\d )\b': r'col-sm-\1', r'\bcol-md-(\d )\b': r'col-md-\1', r'\bcol-lg-(\d )\b': r'col-lg-\1', r'\bcol-xl-(\d )\b': r'col-xl-\1', r'\bbtn-default\b': 'btn-secondary', r'\bimg-responsive\b': 'img-fluid', r'\bimg-circle\b': 'rounded-circle', r'\bimg-rounded\b': 'rounded', r'\bpanel\b': 'card', r'\bpanel-heading\b': 'card-header', r'\bpanel-title\b': 'card-title', r'\bpanel-body\b': 'card-body', r'\bpanel-footer\b': 'card-footer', r'\bpanel-primary\b': 'card bg-primary text-white', r'\bpanel-success\b': 'card bg-success text-white', r'\bpanel-info\b': 'card text-white bg-info', r'\bpanel-warning\b': 'card bg-warning', r'\bpanel-danger\b': 'card bg-danger text-white', r'\bwell\b': 'card card-body', r'\bthumbnail\b': 'card card-body', r'\blist-inline\s*>\s*li\b': 'list-inline-item', r'\bdropdown-menu\s*>\s*li\b': 'dropdown-item', r'\bnav\s navbar\s*>\s*li\b': 'nav-item', r'\bnav\s navbar\s*>\s*li\s*>\s*a\b': 'nav-link', r'\bnavbar-right\b': 'ml-auto', r'\bnavbar-btn\b': 'nav-item', r'\bnavbar-fixed-top\b': 'fixed-top', r'\bnav-stacked\b': 'flex-column', r'\bhidden-xs\b': 'd-none', r'\bhidden-sm\b': 'd-sm-none', r'\bhidden-md\b': 'd-md-none', r'\bhidden-lg\b': 'd-lg-none', r'\bvisible-xs\b': 'd-block d-sm-none', r'\bvisible-sm\b': 'd-none d-sm-block d-md-none', r'\bvisible-md\b': 'd-none d-md-block d-lg-none', r'\bvisible-lg\b': 'd-none d-lg-block d-xl-none', r'\bpull-right\b': 'float-right', r'\bpull-left\b': 'float-left', r'\bcenter-block\b': 'mx-auto d-block', r'\binput-lg\b': 'form-control-lg', r'\binput-sm\b': 'form-control-sm', r'\bcontrol-label\b': 'col-form-label', r'\btable-condensed\b': 'table-sm', r'\bpagination\s*>\s*li\b': 'page-item', r'\bpagination\s*>\s*li\s*>\s*a\b': 'page-link', r'\bitem\b': 'carousel-item', r'\bhelp-block\b': 'form-text', r'\blabel\b': 'badge', r'\bbadge\b': 'badge badge-pill' }
This function is the heart of our script. It takes two parameters:
The class_mappings dictionary is crucial. It maps Bootstrap 3 class patterns (as regex) to their Bootstrap 4 equivalents. For example, col-xs-* becomes just col-* in Bootstrap 4.
def replace_class(match): classes = match.group(1).split() updated_classes = [] for cls in classes: replaced = False for pattern, replacement in class_mappings.items(): if re.fullmatch(pattern, cls): updated_cls = re.sub(pattern, replacement, cls) updated_classes.append(updated_cls) replaced = True break if not replaced: updated_classes.append(cls) return f'class="{" ".join(updated_classes)}"' if file_type in ['cshtml', 'html']: return re.sub(r'class="([^"]*)"', replace_class, content)
This part handles the replacement of classes in HTML and Razor files:
def replace_js_selectors(match): full_match = match.group(0) method = match.group(1) selector = match.group(2) classes = re.findall(r'\.[-\w] ', selector) for i, cls in enumerate(classes): cls = cls[1:] for pattern, replacement in class_mappings.items(): if re.fullmatch(pattern, cls): new_cls = re.sub(pattern, replacement, cls) classes[i] = f'.{new_cls}' break updated_selector = selector for old_cls, new_cls in zip(re.findall(r'\.[-\w] ', selector), classes): updated_selector = updated_selector.replace(old_cls, new_cls) return f"{method}('{updated_selector}')" if file_type == 'js': js_jquery_methods = [ 'querySelector', 'querySelectorAll', 'getElementById', 'getElementsByClassName', '$', 'jQuery', 'find', 'children', 'siblings', 'parent', 'closest', 'next', 'prev', 'addClass', 'removeClass', 'toggleClass', 'hasClass' ] method_pattern = '|'.join(map(re.escape, js_jquery_methods)) content = re.sub(rf"({method_pattern})\s*\(\s*['\"]([^'\"] )['\"]\s*\)", replace_js_selectors, content) return content
This section handles updating class names in JavaScript files:
def process_file(file_path): try: with open(file_path, 'r', encoding='utf-8') as file: content = file.read() file_type = file_path.split('.')[-1].lower() updated_content = update_bootstrap_classes(content, file_type) if content != updated_content: with open(file_path, 'w', encoding='utf-8') as file: file.write(updated_content) print(f"Updated: {file_path}") else: print(f"No changes: {file_path}") except Exception as e: print(f"Error processing {file_path}: {str(e)}")
This function handles the processing of individual files:
def main(): project_dir = input("Enter the path to your project directory: ") print(f"Scanning directory: {project_dir}") if not os.path.exists(project_dir): print(f"The directory {project_dir} does not exist.") return files_found = False for root, dirs, files in os.walk(project_dir): for file in files: if file.endswith(('.cshtml', '.html', '.js')): files_found = True file_path = os.path.join(root, file) print(f"Processing file: {file_path}") process_file(file_path) if not files_found: print("No .cshtml, .html, or .js files found in the specified directory.") if __name__ == "__main__": main()
The main function ties everything together:
To use the script, simply run it and provide the path to your project directory when prompted. It will then process all relevant files, updating them as necessary.
python bootstrap_migrator.py
While this script automates a significant portion of the migration process, it's important to note that it's not a complete solution. You should still:
This script provides a powerful, automated way to handle a large part of the Bootstrap 3 to 4 migration process, saving developers significant time and reducing the chance of manual errors. It represents the first step in our journey to modernize our legacy C# .NET codebase. Once we've successfully migrated to Bootstrap 4 and ensured stability, we'll tackle the next phase: moving from Bootstrap 4 to 5.
Remember, while automation is incredibly helpful, it's not a substitute for understanding the changes between Bootstrap versions. Use this script as a powerful aid in your migration process, but always couple it with your expertise and thorough testing.
Happy migrating!
Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.
Copyright© 2022 湘ICP备2022001581号-3