verbling / assetflow

Asset deployment pipeline
MIT License
44 stars 10 forks source link

Prepend CDN URL for assets automatically [suggestion] #2

Closed mortenbo closed 11 years ago

mortenbo commented 11 years ago

Hi,

Awesome lib!

Currently, to allow absolute CDN URL's you'd have to pipe the paths through a function or an object that references the manifest file and in turn returns an absolute CDN URL for the asset path.

I see three issues with that solution:

Why not instead apply a post search & replace task that runs through all the files in the project and replaces all paths from the manifest file?

I think this will make it more flexible and easier to get started.

thanpolas commented 11 years ago

There are multiple ways to do the same, depending on needs and context.

Frontend Javascript

Use the assetBundle task and consume that JSON natively

Stylus

Concentrate all __ASSET() keywords in a single file that contains variables, then use the variables in the rest of your stylus files...

#loader-logo
    width 95px
    height 100px
    margin 0 auto spacing
    background url(@img_logo) no-repeat center center

I'm not very familiar with how Stylus does variables but i'm sure you know... Actually i think Stylus can also parse JS? If that's the case then why not consume the manifest.json directly?

Node

Isn't the provided solution of using assetflow with node enough for you? Do you need some extra functionality?

Why not instead apply a post search & replace task that runs through all the files in the project and replaces all paths from the manifest file?

This can be potentially disastrous in so many ways. How the paths are consumed is so radically different when you switch contexts that there simply cannot be a catch-all regex that will do the miracles we all wish could happen.

You can create your own abstractions on top of assetflow in the Frontend JS and Node.js contexts so that is solved right there...

For contexts that do not such processing capabilities (Stylus, LESS, CSS, etc) the assetsReplace task is provided, using the __ASSET() keyword which is configurable.

I know i am iterating what's already there, i have given this issue a lot of thought and has gone through trial and error phases were what you suggest was tried and proved to be a very failure prone solution.

Assetflow has been running on production machines of pretty large installations for a couple months now. Using it implies that you are creating some serious infrastructure for your project. I think it is well worth the time to invest in some patterns that you will follow throughout your project. It will enable scalability and improve readability / maintainability.

mortenbo commented 11 years ago

I really appreciate all the available tools. That's a lot of work!

If all your assets follow a specific pattern such as /assets/img/logo.png and /assets/css/main.css throughout your project, I don't see how it can be disastrous?

The issue for me is that I have to make assetflow a part of my development. I'd prefer it if assetflow was a part of my build process when I want to release my project. But you don't necessarily have to support it in your library as I can use grunt-string-replace instead. Although it would be nifty :-)

thanpolas commented 11 years ago

I cannot expect anyone to follow a specific convention in their asset naming patterns.

Your entire codebase would have to be copied so it can safely be massively searched & replaced. This would result in build / deploy times that exceed 2minutes and can easily be at the 10minutes area.

Anyway you slice it, you gonna need to use some form of abstraction to achieve what you need. Either it be assetflow's facilities, grunt-string-replace or anything else that performs massive copy / search & replace operations.

Everything you need is out there, what is missing is a strategy :)

mortenbo commented 11 years ago

I see the point of it being slow. Us lazy developers don't want that ;-)

I'll give it a shot but I have some follow-up questions:

1) How do you make sure local assets are served in development and CDN assets in production only? 2) When setting up a watch that prepares assets on every file change and copies them to a temporary directory, how do you make that work together with the existing node.js process in development?

My project structure is as follows:

Here's my Gruntfile:

module.exports = function (grunt) {
    'use strict';

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        clean: ['dist', 'dist-assets'],
        copy: {
            main: {
                files: [
                    {
                        expand: true,
                        cwd: 'src/',
                        src: ['**'],
                        dest: 'dist/'
                    }
                ]
            }
        },
        requirejs: {
            std: {
                options: {
                    baseUrl: '<% assets.options.cdnurl %>',
                    appDir: 'dist/public/js',
                    mainConfigFile: 'dist-assets/public/js/main.js',
                    modules: [
                    ],
                    skipModuleInsertion: false,
                    removeCombined: true,
                    optimizeAllPluginResources: true,
                    findNestedDependencies: true
                }
            }
        },
        jshint: {
            options: {
                curly: false,
                immed: true,
                eqeqeq: true,
                latedef: true,
                newcap: true,
                noarg: true,
                sub: true,
                undef: true,
                eqnull: true,
                browser: true,
                devel: true,
                jquery: true,
                laxbreak: true,
                expr: true,
                scripturl: true,
                globals: {
                    define: true,
                    requirejs: true,
                    require: true
                }
            },
            all: ['src/public/**/*.js', '!src/public/js/libs/**/*.js']
        },
        assets: {
            options: {
                rel: 'dist/public/',
                truncateHash: 8,
                manifest: 'dist-assets/manifest.json',
                cdnurl: 'https://id.cloudfront.net/',
                prepend: '/'
            },
            all: {
                options: {
                    rel: 'dist/public'
                },
                src: ['dist/public/js/**', 'dist/public/css/**/*.css', 'dist/public/img/**'],
                dest: 'dist-assets/'
            }
        },
        assetsS3: {
            options: {
                debug: true,
                checkS3Head: true,
                manifest: 'dist-assets/manifest.json',
                key: 'xxx',
                secret: 'xxx',
                bucket: 'xxx',
                access: 'public-read',
                progress: true
            },
            all: {
                options: {
                    maxOperations: 100
                },
                upload: {
                    src: 'dist-assets/**',
                    headers: {
                        'Cache-Control': 'max-age=31536000, public'
                    }
                }
            }
        }
    });

    grunt.loadNpmTasks('grunt-contrib-clean');
    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-requirejs');
    grunt.loadNpmTasks('assetflow');

    grunt.registerTask('build', ['clean', 'copy', 'assets']);
    grunt.registerTask('build:upload', ['clean', 'copy', 'assets', 'assetsS3']);
    grunt.registerTask('default', 'jshint');
    grunt.registerTask('cleaner', 'clean');
};

Thanks.

thanpolas commented 11 years ago

1) How do you make sure local assets are served in development and CDN assets in production only?

Do not have a manifest.json file. If no manifest file exists, Assetflow will fallback to whatever value has been provided as the asset's key. Which means __ASSET(/assets/logo.png) will get replaced to /assets/logo.png.

2) When setting up a watch that prepares assets on every file change and copies them to a temporary directory, how do you make that work together with the existing node.js process in development?

That's a good question, i added the following example to the README:

Files that are produced with the assetsReplace task should be in your codebase and tracked.

For example, here is a typical setup for less files:

Directory Structure

assets-replace.less
assets.less   <--- this is auto-generated by Grunt
main.less
restStyless.less

main.less

@import "assets"

@import "restStyles"
// ....

assets-replace.less

//
// ONLY ADD IMAGES TO assets-replace.less
// IMAGES ADDED TO assets.less WILL BE OVERWRITTEN
//

@asset_img_logo: "__ASSET(/assets/logo.png)";
@asset_img_cover: "__ASSET(/assets/cover.jpg)";

assetsReplace task generates assets.less:

This file is generated by the assetsReplace Grunt task.

//
// ONLY ADD IMAGES TO assets-replace.less
// IMAGES ADDED TO assets.less WILL BE OVERWRITTEN
//

@asset_img_logo: "/assets/logo.png";
@asset_img_cover: "/assets/cover.jpg";
thanpolas commented 11 years ago

i also added how the restStyles.less` file would look like:

restStyles.less

#header{
  .logo {
    position: relative;
    margin: 0;
    background: url(@asset_img_logo) bottom center no-repeat;
  }

  .cover {
    background: url(@asset_img_cover) no-repeat #577d9c;
    cursor: default;
    padding-left: 35px;
    background-position: 9px 9px;
  }
}
mortenbo commented 11 years ago

Thank you for your helpful answers. I'm gonna close the issue :-)